- 投稿日:2021-02-28T22:46:07+09:00
Laravel ファイル名が日本語の添付ファイルが送れなかった
はじめに
あるプロジェクトでファイル名が日本語のPDFをメールで送信する必要がありました。
公式や記事を見ながら実装したところ、何故か日本語部分だけが送信できないという事象が発生しました。前提
Centos7系 PHP7系LaravelのMailファサード・Mailableクラスを用いて実装しています。
HogeController.phpnamespace App\Http\Controllers; use Illuminate\Http\Request; use App\Mail\SampleMailable; use Mail; public function sendMail() { Mail::to('hogehoge@hogehoge')->send(new SampleMailable()); }SampleMailable.php<?php namespace App\Mail; use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; use Illuminate\Contracts\Queue\ShouldQueue; class SampleMailable extends Mailable { use Queueable, SerializesModels; /** * Create a new message instance. * * @return void */ public function __construct() { // } /** * Build the message. * * @return $this */ public function build() { return $this->view('emails.test') ->from('XXX@XXXX','Test') ->subject('テストメールです。') ->attach(storage_path('app/pdf/テストfile.pdf')); } }事象
送られてきたメールを確認すると、添付されているファイル名が以下のようになっていました。
結果 file.pdf 期待値 テストfile.pdf調査
以下の順序で調査を行いました。
- config.phpのlocaleがjaになっているか
- 参考記事のようにファイル名を参照する際にbasename()を使用しているかどうか
- サーバーのlocaleが正しいか
- php.iniの設定が正しいかどうか
調査結果
php.configの値
config.php'locale' => 'ja',basename()
今回、basenameは未使用。
laravelのソースも確認しましたが、使用されていませんでした。サーバーのlocale
正しい値のようでした。
LANG=ja_JP.utf8 LC_CTYPE="ja_JP.utf8" LC_NUMERIC="ja_JP.utf8" LC_TIME="ja_JP.utf8" LC_COLLATE="ja_JP.utf8" LC_MONETARY="ja_JP.utf8" LC_MESSAGES="ja_JP.utf8" LC_PAPER="ja_JP.utf8" LC_NAME="ja_JP.utf8" LC_ADDRESS="ja_JP.utf8" LC_TELEPHONE="ja_JP.utf8" LC_MEASUREMENT="ja_JP.utf8" LC_IDENTIFICATION="ja_JP.utf8" LC_ALL=php.iniの設定値
php.iniでlocale設定がされていなかったです。
原因
日本語を扱えるようにするにはphp.ini内で設定が必要ですが、デフォルト値のまま変更がされていませんでした。
対処法
効果がなかった対処法
こちらに記載されていたMailableのローカライズを参考にしましたが、期待通りに反映されませんでした。
HogeController.phppublic function sendMail() { Mail::to('hogehoge@hogehoge')->locale('ja')->send(new SampleMailable()); }今回の対処法
大人の事情でサーバー上にあるphp.iniを編集することは叶いませんでしたので、以下の内容で対応しました。
AppServiceProvoder.phpclass AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { setlocale(LC_ALL, 'ja_JP.UTF-8'); } }これでLaravel上の処理は全て日本語対応されるはずです。
ちなみに、LC_ALLは他のLC関連の環境変数よりも優先される環境変数です。別の対処法
php.iniの設定を変更したほうが良いと思います。
php.inimbstring.language = Japanese mbstring.internal_encoding = UTF-8 mbstring.encoding_translation = Off mbstring.http_input = pass mbstring.http_output = pass mbstring.detect_order = UTF-8,SJIS,EUC-JP,JIS,ASCIIこれでもうまくいくと思います。(未検証なので、ご存知の方がいらっしゃいましたら教えていただけると幸いです!)
- 投稿日:2021-02-28T22:38:47+09:00
シーダー実行に失敗したためSQLSTATE[42S22]:のエラーが発生
環境
Laravel6(PHP7.3以上)
MySQL8.0
Laradock(Docker)
Vscodeエラー発生
seederでテストデータを挿入する際に、下記のエラーが出ました。
SQLSTATE[42S22]:PDOException::("SQLSTATE[42S22]: Column not found: 1054 Unknown column 'prduct_name' in 'field list'")エラーの内容
「エラー文の表示からわかるように、フィールド(テーブル)に'prduct_name'というカラムがないですよ。」
と優しく教えてくれています。テーブルにエラー文で表示されたカラムがあるか確認し、Insert文でそのカラムが正しく書けているかを確認してください。
解決方法
自分はInsert文で指定したカラムのスペルがテーブルにあるカラムと違っていたためSQLSTATE[42S22]:が発生しました。
- 投稿日:2021-02-28T20:12:46+09:00
【Chart.js × Laravel】Controllerで返されたデータをJavaScriptで操作するには
Controllerで返された値をJavascriptで操作する方法、Javascriptでchart.jsを使って複数のチャートで動的なデータを表示する方法があまり見当たらなかったのでこちらで紹介。
通常Controllerで返された値(配列)は
@foreach
を使ってbladeで表示できる。test.blade.php// as $key => $valueの場合、 @foreach($array_datas as $key => $value) <div class="box"> <p>keyは{{ $key }}で、valueは{{ $value }}だよ</p> </div> @endforeach // as $dataの場合、 @foreach($array_datas as $data) <div class="box"> <p>それぞれのデータは{{ $data }}として表示されたよ。</p> </div> @endforeachbladeでそのままデータを表示する場合は、上記のようにすれば問題ない。
JSライブラリによっては、出力される属性をid
を指定している。
そのため@foreach
との併用の場合少し工夫がいる。chart.jsも同様で、出力される属性は
id
なのでそのまま@foreach
を使うと最初だけchartが表示されて2回目以降は表示されない。これはidがユニークなものでなければならないため、重複したidは2度目以降読み込まれないから。
なので、idで設定しているものをforeach
で回すときはHTML側で{{$key}}
を設定し、それぞれを独立させる必要がある。また、Javascript側でも読み込めるようにforEach
を使って読み込むid
を指定する。まず初めに、chart.jsをそれぞれで読み込めるようにblade側を以下のように変更する。
test.blade.php@foreach($array_datas as $data) <div class="box"> <canvas id="myBarChart{{ $key }}"></canvas> </div> @endforeachkeyを追加することでユニークなidを
id=myBarChart0
、id=myBarChart1
、id=myBarChart2
、のように生成する。javascript側は
document.getElementById()
でユニークなidを読み込むので、それぞれのidを読み込むように、forEachを使って要素ごとの実行を行う。
なお、$foreach
で定義された$array_datas
は@JSON
を使うことでjavascriptでも操作できる。test.blade.php(chart.js以下にscriptを設ける)<script> const array_datas = @JSON($array_datas); //bladeの$array_datasをjavascriptで読み込む const data_keys = Object.keys(array_datas); // それぞれのkeyを取得 data_keys.forEach(el =>{ const chart_id = "myBarChart" + el; // elはdata_keysのそれぞれのkey var ctx = document.getElementById(chart_id); var myBarChart = new Chart(ctx, { // それぞれのidごとにchartが生成される。 }); }); </script>ここまでで
@foreach
の中にあるchartは2回目以降も表示される。
javascript側で常にkey
をみることで、bladeの$keyと連動させることができる。とはいえ、これでは同じチャートの情報を複製しているにすぎない。
ここからはそれぞれのchartでkeyごとに別々のデータを表示する方法を紹介。
chart.jsでは表示されるデータの値を以下のようにdatasets以下のdata配列から参照する。test.blade.phpvar myBarChart = new Chart(ctx, { type: 'bar', data: { labels: ['8月1日', '8月2日', '8月3日', '8月4日', '8月5日', '8月6日', '8月7日'], datasets: [ { label: 'A店 来客数', data: [62, 65, 93, 85, 51, 66, 47], backgroundColor: "rgba(219,39,91,0.5)" },{ label: 'B店 来客数', data: [55, 45, 73, 75, 41, 45, 58], backgroundColor: "rgba(130,201,169,0.5)" },{ label: 'C店 来客数', data: [33, 45, 62, 55, 31, 45, 38], backgroundColor: "rgba(255,183,76,0.5)" } ] }, . . .例えば、Controllerで取得した配列データが連想配列で、そのデータがkey以下に複数の配列が存在する以下のような場合、
それぞれのkey(0,1,2)ごとにデータ("Aサイト" => array3)を参照してchartで表示したい。
こんなときはJavascriptでいったんkeyごとに配列を生成して、それを参照するようにchart.jsに記述する。const array_datas = @JSON($array_datas); const data_keys = Object.keys(array_datas); console.log(data_keys); // ["0", "1", "2"] data_keys.forEach(el =>{ const month = []; const clicks = []; const imps = []; const chart_id = "myBarChart" + el; const array_data = Object.values(array_datas[el]); console.log(array_data); for(var i = 0; i < array_data[0].length; i++){ month.push(array_data[0][i]['month']); clicks.push(array_data[0][i]['click']); imps.push(array_data[0][i]['imps']); } console.log(month); // ["2020-12", "2020-11", "2020-10"] console.log(clicks); // [3, 8, 1] console.log(imps); //[36, 52, 24] var ctx = document.getElementById(chart_id); var myBarChart = new Chart(ctx, { type: 'bar', data: { labels: month, datasets: [ { label: 'クリック数', data: clicks, backgroundColor: "rgba(219,39,91,0.5)", },{ label: '表示回数', data: imps, backgroundColor: "rgba(130,201,169,0.5)" }, ] }, options: { scales: { yAxes: [{ //y軸 ticks: { suggestedMax: 80, suggestedMin: 0, stepSize: 10, } }], xAxes: [{ //X軸 ticks: { font: { size: 3, }, padding: 0 } }] }, layout: { padding: { left: 0, right: 0, top: 0, bottom: 0 } }, plugins: { datalabels: { // 共通の設定はここ font: { size: 14, color: 'rgba(200,60,60,1)', }, anchor: 'end', align: 'end', } }, } }); });bladeも若干調整する。
test.blade.php@foreach($array_datas as $key => $value) <div class="chart-box"> <div>keyの番号{{$key}}番目のチャート情報</div> @foreach ($value as $index_name => $item) <div>~~ {{$index_name}}の結果 ~~</div> @endforeach <canvas id="myBarChart{{ $key }}"></canvas> </div> @endforeach <style> .chart-box { display: inline-block; text-align: center; width: 300px; margin: 30px; background: #f7f7f7; } </style> <script src="{{ asset('js/Chart.bundle.js') }}"></script> <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@0.7.0"></script>結果、以下のようにそれぞれの値をchartで表示することができる。
おまけ
chart.jsのy軸のラベルはデフォルトでは数値を記述することで,Max,Min,Stepを設定できる。できれば、配列のデータごとにlabelも可変されるとよりみやすくなる。その場合は、下記のように関数を設定して動的にlabelを設定するといい感じのチャートが完成する。
yAxes: [{ //y軸 ticks: { suggestedMax: 80, // ←ここ suggestedMin: 0, stepSize: 10, // ←ここ } }], // ********* 以下のように変更 ********* // yAxes: [{ //y軸 ticks: { suggestedMax: adjastSuggestedMax(month,clicks,imps) suggestedMin: 0, stepSize: adjastSuggestedStep(month,clicks,imps), } }], . . . }); // 以下2つの関数を追加 function adjastSuggestedMax(sc_keyword_month,sc_keyword_clicks,sc_keyword_imps){ max_clicks = Math.max.apply(null, sc_keyword_clicks); max_imps = Math.max.apply(null, sc_keyword_imps); all_max_num = []; all_max_num.push(max_clicks,max_imps); max_num = Math.max.apply(null, all_max_num); if(max_num < 10){ var suggestedMax = 10; } else if(10 <= max_num){ var suggestedMax = max_num + 20; } return suggestedMax; }; function adjastSuggestedStep(sc_keyword_month,sc_keyword_clicks,sc_keyword_imps){ max_clicks = Math.max.apply(null, sc_keyword_clicks); max_imps = Math.max.apply(null, sc_keyword_imps); all_max_num = []; all_max_num.push(max_clicks,max_imps); max_num = Math.max.apply(null, all_max_num); if(max_num < 10){ var suggestedStep = 10; } else if(10 <= max_num){ var suggestedStep = 30; } return suggestedStep; };こうすることで配列の数字の中から最大値を検知してそれに合わせてラベルやステップを表示することができる。if条件をさらに細かくすれば自分好みの可変もできる。
まとめ
今回はbladeのデータをJavascriptで扱う方法と個別チャートの表示方法を紹介しました。
chart.jsに限らず多くのライブラリで同じような方法で個別に生成したりできるので、他のライブラリでも挑戦してみてください。
- 投稿日:2021-02-28T18:02:10+09:00
LaravelのエラーSQLSTATE[HY000] [2002]を解決する
環境
Laravel6(PHP7.3以上)
MySQL8.0
Laradock(Docker)
Vscode前提
php artisan migrateを実行するとSQLSTATE[HY000] [2002]のエラーが出る。
SQLSTATE[HY000] [2002]のエラーを調べてみると
環境構築で.envとdocker-compose.ymlの両ファイルにて設定がズレが発生していることが原因
とのことでした。試したこと
.envとdocker-compose.ymlの両ファイルをコマンドにて開き
DB_CONNECTION=mysql DB_HOST=mysql DB_PORT=3306 DB_DATABASE=default DB_USERNAME=default DB_PASSWORD=secretユーザーネームなどを両ファイル一致するようにしたが、問題解決には至らず。
解決策
仮想環境内でphp artisan migrateを実行するとsuccessの文字が浮かび上がります!
SQLSTATE[HY000] [2002]のエラーの原因は作成したコンテナに入らずにphp artisan migrateを実行していたことでした。コマンドで、プロジェクトファイル上ではなく、作成した仮想環境内に入ってphp artisan migrateをする必要があるんそうです。
これで解決!!
- 投稿日:2021-02-28T17:59:04+09:00
Laravelで将棋アプリを作りたい(第三回)
今回はデータベース連携と玉将配置
目次
1.データベース作成
2.駒の移動先をデータベースに保存
3.駒の位置をデータベースから取得
4.先手後手の導入と玉将配置1. データベース作成
駒の移動履歴を記録するためにデータベースを使用する。
今回は以下のようなgame_records
テーブルを作成したい。
カラム データ型 説明 id bigIncrement turn integer 手番(0:先手、1:後手) piece integer 駒番号(0:王将) square varchar マス番号 MySQLを起動してログインする。
ターミナル$ mysql.server start Starting MySQL ... SUCCESS! $ mysql -uroot -p Enter password:
shogi_app
というデータベースを新しく作成する。この中に、game_records
テーブルが作成されるようにする。ターミナルmysql> create database shogi_app;Laravelの.envファイルに記されているデータベース設定を必要に応じて修正する
.envDB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=shogi_app DB_USERNAME=root DB_PASSWORD={ログイン時に使用しているパスワード}モデルを使ってデータベース操作を行う。
まず、アプリ配下で以下を実行してモデルとマイグレーションファイルを作成する。ターミナル$ php artisan make:model GameRecord --migration作成されたマイグレーションファイルのupメソッドでテーブルを定義する。
databases/migrations/2021_02_xx_xxxx_create_game_records_table.phppublic function up() { Schema::create('game_records', function (Blueprint $table) { $table->bigIncrements('id'); $table->integer('turn'); $table->integer('piece'); $table->string('square'); $table->timestamps(); }); }マイグレーションを実行してテーブルを作成!
ターミナル$ php artisan migrate
2. 駒の移動先をデータベースに保存
indexメソッドにPOST送信されてくる移動先の駒情報をDBに保存する。
まず、コントローラにモデルの使用を宣言する。app/Http/Controllers/ShogiController.phpuse Illuminate\Http\Request; use App\Models\GameRecord; // 追加POST送信された時の保存処理は以下のように書ける。
new モデル名
でモデルのインスタンスを新規に作成して、操作・保存できる。$data = new GameRecord; $data->turn = $request->input('turn'); // 手番 $data->piece = 0; // 王将 $data->square = $request->input('move'); // 移動先マス $data->save();3. 駒の位置をデータベースから取得
モデルを使って簡単にデータを取得できる。
例:
$bLast
として直近の先手のデータを取得する場合
idの逆順に並べ【orderBy】、turnが0(先手)のデータを検索して【where】、一件だけを取得【first】で実現できる。$bLast = GameRecord::where('turn', 0)->orderBy('id', 'desc')->first();これを使ってコントローラーでデータベースから駒の位置を取得して、それをビューに渡すようにする。
ここで、データがない場合のことを考えて、
config
フォルダに初期値を設定しておく。(配列で定義)config/const.php<!-- 新規作成 --> <?php return [ 'start' => [ 'bKing' => [ // 先手の初期値 'turn' => 0, 'piece' => 0, 'square' => 59, ], 'wKing' => [ //後手の初期値 'turn' => 1, 'piece' => 0, 'square' => 51, ], ] ];config配下に記述した定数は以下のようにコントローラーから呼び出すことができる。
例:先手の初期値を取得config('const.start.bKing')以上で
「(最初だけ)初期値をconst.php
から取得」→「移動したらデータベースに保存」→「直近のデータをデータベースから取得」
を繰り返して、駒の移動を実現する。4. 先手後手の導入と玉将配置
将棋は二人でやるものだと気づいたので、先手・後手の違いをつける。
indexメソッドとselectメソッド共に、先手「王将」と後手「玉将」それぞれのデータを取得し、
$bKing
、$wKing
としてビューに渡す。selectメソッドでは(初期値またはデータベースから)取得した「選択中の駒」の情報が渡されている。それを参考に先手と後手で処理を分けた際に
$turn
という変数を手番に合わせて定義して、ビューに渡す。selectページでは受け取った
$turn
も以下のような隠しフォームでindexページにPOST送信するようにする。echo '<input type="hidden" value="' . $turn . '" name="turn"></input>';ビューで後手の「玉将」を表示させる。
indexビューの修正箇所
resources/views/shogi/index.blade.phpif ($square == $bKing['square']) { // 王将 echo '<p class="piece row turnb square' . $square . '" id="square' . $square . '"><a style="text-decoration:none" href="' . action('App\Http\Controllers\ShogiController@select', $bKing['turn'] . ':' . $bKing['piece'] . ':' . $r . ':' . $c) . '">王</a></p>'; } elseif ($square == $wKing['square']) { // 玉将 echo '<p class="piece row turnw square' . $square . '" id="square' . $square . '"><a style="text-decoration:none" href="' . action('App\Http\Controllers\ShogiController@select', $wKing['turn'] . ':' . $wKing['piece'] . ':' . $r . ':' . $c) . '">玉</a></p>'; }selectビューの修正箇所
resources/views/shogi/select.blade.phpif ($square == $bKing['square']) { // 駒マス「王将」 echo '<p class="piece row turnb square' . $square . ' selected" id="square' . $square . '">王</p>'; } elseif ($square == $wKing['square']) { // 駒マス「玉将」 echo '<p class="piece turnw row square' . $square . ' selected" id="square' . $square . '">玉</p>'; }玉将の文字を反転するために追加したCSS
public/css/style.css.turnw { transform:rotate(180deg); }手番別の記述を追加したindexメソッド
app/Http/Controllers/ShogiController.phppublic function index(Request $request) { if ($request->isMethod('post')) { // POST時の処理 $data = new GameRecord; $data->turn = $request->input('turn'); // 手番 $data->piece = 0; // 王将 $data->square = $request->input('move'); // 移動先マス $data->save(); } // 先手番の最新手を取得 $bLast = GameRecord::where('turn', 0)->orderBy('id', 'desc')->first(); $bKing = (!empty($bLast['square'])) ? $bLast : config('const.start.bKing'); // 後手番の最新手を取得 $wLast = GameRecord::where('turn', 1)->orderBy('id', 'desc')->first(); $wKing = (!empty($wLast['square'])) ? $wLast : config('const.start.wKing'); return view('shogi/index', compact('bKing', 'wKing')); }手番別の記述を追加したselectメソッド
public function select($piece) { // $pieceには 0:0:5:9 のように手番、駒番、列、行が「:」でくっついた形が入る。 $king = array(); $select = explode(":", $piece); $king = array( 'turn' => $select[0], 'piece' => $select[1], 'square' => $select[2] . $select[3], ); // 移動可能マス配列としてselectページに渡す $ways = array(); // 王将の移動可能マスを割り出して、$waysに追加する。 for ($c = $select[3]-1; $c < $select[3]+2 ; $c++) { for ($r = $select[2]-1; $r < $select[2]+2 ; $r++) { $ways[] = $r . $c; } } $way = array_unique($ways); if ($select[0] == 0) { // 先手番の処理 $bKing = $king; $wLast = GameRecord::where('turn', 1)->orderBy('id', 'desc')->first(); $wKing = (!empty($wLast['square'])) ? $wLast : config('const.start.wKing'); $turn = 0; } elseif ($select[0] == 1) { // 後手番の処理 $wKing = $king; $bLast = GameRecord::where('turn', 0)->orderBy('id', 'desc')->first(); $bKing = (!empty($bLast['square'])) ? $bLast : config('const.start.bKing'); $turn= 1; } return view('shogi/select', compact('bKing', 'wKing', 'way', 'turn')); }以上でデータベース連携 & 玉将配置までできた。今回はここまで。
- 投稿日:2021-02-28T17:12:06+09:00
【Laravel】route関数を使用する場合のSSL化対策
SSL化する前の状態
bladeテンプレートのformのaction属性を
route
関数で記載。
route
関数についてはこちらの記事で説明しています。<form method="post" action="{{ route('profile.create') }}">上記の記述だと、URLが
http://・・・
になってしまいます。
SSL化されていませんので遷移先の画面では遷移先では下記画面が表示されます。
ちなみに、「このまま送信」ボタンをクリックするとエラーになります。
エラーの原因は、action属性で指定したroute先にリダイレクトされているため(GETリクエストのため)です。(formのaction属性にパスを直で指定した場合は、URLは
https://・・・
でした。なのでroute関数を使用しない場合は、今回のSSL化対策はしなくても大丈夫だと思います。)SSL化対策
App/Providers/AppServiceProvider.php
に以下を追記する必要があります。App/Providers/AppServiceProvider.php<?php namespace App\Providers; use Illuminate\Routing\UrlGenerator; class AppServiceProvider extends ServiceProvider public function boot(UrlGenerator $url) { $url->forceScheme('https'); } }追記した後に、再度URLを確認すると
https://・・・
になっていました。おわりに
今回、
route
関数を使用した時のSSL化対策を記載しました。似たような関数でurl
関数がありますが、こちらもSSL化対策が必要だそうです。対策する場合は、下記記事が参考になると思います。
Laravelで作ったサービスをSSL化した時にやったことと参考記事一覧参考
- 投稿日:2021-02-28T16:05:56+09:00
【Laravel】Formの項目が多い場合にsessionに保存したエラーメッセージが表示されない問題
背景
LaravelでRequestFormを使ったバリデーションを実装中、
空のFormを送信するとバリデーションにひっかかって正しくリダイレクトされるが、
FormRequestに設定したエラーメッセージが表示されない問題が発生。
原因を切り分けてみるとFormRequestの設定に問題はなく、対象のformの項目数を減らすと正しくエラーメッセージが表示されたので、
項目が一定数以上あるフォームで発生するのは分かったのですが、
根本的な原因が分からずに詰まってしまったのでその原因と解決策をメモ。
結論
/config/session.php
の'driver'
の項目にfileを指定することで解決しました。
.envの環境変数で指定している場合は対象の値をfileに変更すると設定できます。
(※Dockerを利用している場合はDockerfileの環境変数の設定も確認)結論(詳細)
こちらに似たような質問と答えが載ってました。
stackoverflow - Laravel form validation issues (session size limit?)RequestFormのエラーメッセージはsessionに保存されてviewに渡されており、
sessionドライバーにcookieを指定しているとcookieの容量の上限で保存できないのが原因のため、sessionドライバーにfileを使うと解決するとのことでした。
sessionドライバーの指定は
/config/session.php
の'driver'
の項目で指定できるので、cookie
を使っているとをfile
に書き換えて読み込み直すと変更できます。/config/session.php<?php use Illuminate\Support\Str; return [ /* |-------------------------------------------------------------------------- | Default Session Driver |-------------------------------------------------------------------------- | | This option controls the default session "driver" that will be used on | requests. By default, we will use the lightweight native driver but | you may specify any of the other wonderful drivers provided here. | | Supported: "file", "cookie", "database", "apc", | "memcached", "redis", "dynamodb", "array" | */ 'driver' => env('SESSION_DRIVER', 'file'), /* ~~~~~ 省略 ~~~~~~ */ ];.envの環境変数で指定してる場合はそちらの値(僕の場合は
SESSION_DRIVER
)にfile
を指定すると変更できます。SESSION_DRIVER=file余談
僕の環境の場合、上記のサイトのように
/config/session.php
と.env
のSESSION_DRIVER
の値はどちらもfileを指定していたのですが、Dockerfileの環境変数の設定でSESSION_DRIVER
にcookie
を指定していたため、反映されずに詰まってました。引用
- 投稿日:2021-02-28T15:15:14+09:00
LaradockでDuskやろうとしたらちょっと手間が必要だった件
そもそもの話
最近、Laradock触ってないなぁ、と思ったので、試しにDuskを動かそうと思ったことから始まりました。
環境はすでに作成しているものを使っています。やってみたいこと
- Laravelをサブディレクトリとして構築
- Duskのテストが通る
とりあえずの前提条件
- Webサーバのrootとしては、
/var/www/public
を指定- Laravelのrootは
/var/www/public/lara1
、URLもhttp://localhost/lara1
でアクセスできるようにする- 他の設定は標準(Seleniumアクセスのポートは4444で、など)
Laradock起動コマンド
Webサーバはnginx、データベースはmysqlを使います。
Chromeはseleniumコンテナに入っているので同時に起動します。
あとは適当にdocker-compose up workspace nginx mysql seleniumDusk導入まで
やり方は2つの参考記事をもとに対応。
参考1: https://qiita.com/amymd/items/0a5f2705e29972d0d22e
参考2: https://qiita.com/hrkbyc/items/755e9799c205c092c323コマンドは以下の順番で処理していきます。
cd /var/www/public composer create-project laravel/laravel lara1 --prefer-dist cd lara1 composer install composer require --dev dusk composer update # 念の為 npm install npm run dev php artisan dusk:installキモは、
AppServiceProvider.phpの修正
Duskをプロバイダに登録するコードを追加します。
AppServerProvicer.php<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Laravel\Dusk\DuskServiceProvider; // Duskの参照を追加 class AppServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { // Duskのプロバイダを登録 if ($this->app->environment('local', 'testing')) { $this->app->register(DuskServiceProvider::class); } } /** * Bootstrap any application services. * * @return void */ public function boot() { // } }DuskTestCase.php の修正
baseUrlを所定のURLに、driverをSeleniumのサーバーに変更します。
DuskTestCase.php<?php namespace Tests; use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; use Laravel\Dusk\TestCase as BaseTestCase; abstract class DuskTestCase extends BaseTestCase { use CreatesApplication; // baseUrlは別サーバのサブディレクトリになるため明記 protected function baseUrl() { return 'http://nginx/lara1'; } /** * Prepare for Dusk test execution. * * @beforeClass * @return void */ public static function prepare() { if (! static::runningInSail()) { static::startChromeDriver(); } } /** * Create the RemoteWebDriver instance. * * @return \Facebook\WebDriver\Remote\RemoteWebDriver */ // seleniumコンテナのChromeを使用する protected function driver() { return RemoteWebDriver::create( 'http://selenium:4444/wd/hub', DesiredCapabilities::chrome() ); } /** * Determine whether the Dusk command has disabled headless mode. * * @return bool */ protected function hasHeadlessDisabled() { return isset($_SERVER['DUSK_HEADLESS_DISABLED']) || isset($_ENV['DUSK_HEADLESS_DISABLED']); } }さあテスト開始…ところが!?
php artisan duskところが、エラーを出してしまうわけです。
Duskはちゃんと動いているようですが、どうやらLaravelのアプリがまともに動いていないと予想しました。
そこで、参考記事を元にnginxの設定をいじることにしました。参考:https://qiita.com/saba1024/items/2d50fb8284d699924360
対策とってみた
default.confの設定変更
vi /etc/nginx/sites-available/default.conf先の記事で参照していたnginxの設定ではサブディレクトリを考慮していなかったので、色々設定を変えました。
最終的にはこういう形になりました。
特にハマったのはSCRIPT_FILENAME。
index.phpの場所が変わっているのにすぐに気づきませんでした…。
パスの指定を現在の配置に合わせました。fastcgi_param SCRIPT_FILENAME $document_root/public/index.php;あと、fastcgi_passの指定。
もともとあったLaravel向けの設定を読んでみて、php-upstreamコンテナを指定すれば良いことがわかりました。fastcgi_pass php-upstream;あとは、locationやtry_filesの設定に注意して出来上がりました。
default.conflocation ~ ^/lara1((/)?(.+))?$ { root /var/www/public/lara1; try_files $1 /lara1/index.php?$query_string; location ~ ^/lara1/index.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root/public/index.php; fastcgi_param PATHINFO $fastcgi_path_info; fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass php-upstream; fastcgi_index index.php; } }そして、nginxの再起動…エラーなし。
nginx -s reloadこれでLaravelが動くだろう…世の中そんなに甘くありませんでした。
書き込み権限の変更
…Dockerのコマンドラインではrootなのに、nginx上ではnginxユーザで動かしているのをすっかり忘れていました。
storageディレクトリに書き込み権限を付記します。cd /var/www/public/lara1 chmod -R a+w storageさあリベンジ!
ブラウザでは無事Laravelのアプリが表示されました。
続いてDuskでは…root@6580d8cd10f5:/var/www/public/lara1# php artisan dusk PHPUnit 9.5.2 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 00:10.353, Memory: 20.00 MB OK (1 test, 1 assertion)動いたー! やったー!
まとめ
過去の知見となんとか駆使すればなんとかなるということです、はい。
- 投稿日:2021-02-28T14:40:44+09:00
PHP8(Laravel8)でデスクトップアプリケーションを作る方法
今回はPHP8,Laravel8にてデスクトップアプリケーションを作る方法を紹介します。
phpdesktop( https://github.com/cztomczak/phpdesktop )を使うことによってWEB技術によってデスクトップアプリケーションを作ることが可能になります。
早速、起動するまでの手順を書いていきます。
ちなみに自分はwindows環境です。phpdesktopをダウンロードする
githubのreadmeにOSごとの最新バージョンのReleaseへのリンクがあるので自分の環境に合わせたものをダウンロードして解凍します。
中身はこんな感じ
phpのバージョンを8.0にする
そのままだと、PHPのバージョンが古くてLaravel8が使えません。
Laravel8では最低でもバージョン7.3以上が必要です。
今回はphp8にしたいと思いますがバージョ使いたいバージョンでも大丈夫です。
PHPのダウンロードページに行き、使うバージョンをダウンロードします。
Windowsの場合はhttps://windows.php.net/download#php-8.0 にあるx64 Non Thread Safeでないと行けないので注意!そして、phpdesktopフォルダ内にあるphpフォルダの中身をダウンロードしたものに置き換えます。
Laravel8を用意する
laravel8のprojectを用意します。
自分はwindows環境なのですが、windowsにcomposerやら何やら入れるのがめんどくさかったので
vagrantのcentos7上で色々やってます。
予め環境が用意されている場合はwindows上でやっても問題ないかと思います。まず、gitからlaravel8を落としてくる
デフォルトブランチが8.xなのでそのままで。[root@localhost vagrant]# git clone https://github.com/laravel/laravellaravelフォルダに行きcomposerで色々インストールする
[root@localhost vagrant]# cd laravel/ [root@localhost laravel]# composer install.env ファイルを用意する
[root@localhost laravel]# cp .env.example .envアプリケーションのkeyを発行する
[root@localhost laravel]# php artisan key:generateこれでOK
phpdesktopの設定
phpdesktopのwwwフォルダ内に
laravelフォルダ内のものを全部移動させます。
settings.jsonをいじる
"web_server": { ... "www_directory": "www/public", ... "404_handler": "/index.php"phpdesktop-chrome.exeを実行すると
起動できました!!
- 投稿日:2021-02-28T12:34:35+09:00
【Laravel】名前付きルートの使い方について
名前付きルートとは
名前付きルートは特定のルートへのURLを生成したり、リダイレクトしたりする場合に便利です。ルート定義にnameメソッドをチェーンすることで、そのルートに名前がつけられます。
(公式より)ルート定義に名前を付ければ、命名したルート名を指定することでURLの呼び出しができます。
つまり、コントローラーやビューの中でURLを呼び出すときは直にパスを書く必要はありません。ルート名を指定することでURLを呼び出すことができます。使い方
名前付きルートの設定、呼び出しの方についてまとめました。
名前付きルートの設定
かなりシンプルです。各ルート定義の行末にnameメソッドでルート名を付けるだけです。
(※ルート名は一意である必要があります)web.php// ルート名の書き方 Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home'); Route::get('/user/{id}/profile', [App\Http\Controllers\UserProfileController::class, 'show'])->name('profile.show'); Route::post('/user/{id}/profile', [App\Http\Controllers\UserProfileController::class, 'create'])->name('profile.create');設定が出来たら、あとは呼び出し側でルート名を指定します。
Controllerで名前付きルートの呼び出し
// URLの生成 $url = route('home'); // リダイレクトの生成 return redirect()->route('profile.show', ['id' => 1]);名前付きルートでパラメータを定義している場合は、
route
関数の第2引数でパラメーターを指定する必要があります。指定されたパラメーターは自動的にURLの正しい場所へ埋め込まれます。Viewで名前付きルートの呼び出し
// formでのルート指定 <form method="post" action="{{ route('profile.create', ['id' => 1]) }}"> // hrefでのルート指定 <a href="{{ route('profile.show', ['id' => 1]) }}">プロフィールを見る</a>注意点
注意すべき点は、ルート名は常に一意である必要があります。
重複があると、あとに命名されたルートが優先されてしまいます。web.php// 重複のあるルート Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('profile'); Route::get('/user/{id}/profile', [App\Http\Controllers\UserProfileController::class, 'show'])->name('profile');// リダイレクトでルート名を呼び出し return redirect()->route('profile');この場合、リダイレクトで呼ばれるルートは
Route::get('/user/{id}/profile', [App\Http\Controllers\UserProfileController::class, 'show'])->name('profile')
になります。呼び出しの時にパラメータを渡していないので、エラーが生じます。メリット
名前付きルートを利用するメリットは挙げてみました。
- URLを指定する時に、長々とパスを書く必要がなくなりますのでタイプミスを回避できます。
- パス変更があっても、変更箇所はルート定義ファイルの
web.php
のみ。影響範囲が小さく済みます。参考
- 投稿日:2021-02-28T10:10:47+09:00
[Lalavel6 Blade]Foreachループ内のFormファサードで最初のフォームがレンダリングされないことについて
TL;DR(読むのがしんどい人へ)
- Formファサードをforeachディレクティブ内で使用すると、1行目だけボタンがうまくレンダリングされず、機能しない。
- ほかのところでFormファサード使ってるので、ここも統一したいと考えて悪戦苦闘した結果…
@foreach($items as $item) @if($loop->first) <form action=""></form> @endif {{Form:: ~~~~~~}}
- という感じで1行目だけ空のフォームを用意してあげることでちゃんと表示できた。以上。
症状が生じた環境
$ php -v PHP 8.0.0 (cli) (built: Nov 27 2020 12:26:05) ( NTS ) Copyright (c) The PHP Group Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies with Zend OPcache v8.0.0, Copyright (c), by Zend Technologies $ php artisan -V Laravel Framework 6.20.16状況を詳しく説明
LaravelのBlade内でフォームアクションを書くときに便利なFormファサードですが,データの行ごとにアクションを表示しようとしたときに1行目だけレンダリングされない問題が生じました。
具体的には@foreach ($items as $item) …… <td> <a href="{{ action('ItemController@show', [$item]) }}"> <button type="button" class="btn btn-primary">詳細</button> </a> <a href="{{ action('ItemController@edit', [$item]) }}"> <button type="button" class="btn btn-primary">編集</button> </a> <div style="display: inline-flex"> {{ Form::open(['method' => 'delete', 'url' => action('ItemController@destroy', [$item])]) }} {{ Form::submit('削除', ['class' => 'btn btn-danger']) }} {{ Form::close() }} </div> </td> @endforeachこのように削除ボタンを用意しています。
簡単に説明すると、Itemのリストをforeachで表示していて、その行の右端にアクションボタンとして、詳細、編集、削除ボタンを用意しています。
こうすると,
1行目の削除ボタンがちょっとだけ浮いてますね。そのボタンだけ押しても機能しません。ボタンを増やすと
<div style="display: inline-flex"> {!! Form::open(['method' => 'DELETE', 'url' => action('ItemController@destroy', [$book])]) !!} {!! Form::submit('削除', ['class' => 'btn btn-danger']) !!} {!! Form::close() !!} {{ Form::open(['method' => 'DELETE', 'url' => action('ItemController@destroy', [$book])]) }} {{ Form::submit('削除', ['class' => 'btn btn-danger']) }} {{ Form::close() }} </div>
どうした1行目。。。。
この場合1行目左下のボタン以外は反応します。ちなみに,開発者ツールは、
//1行目 <input name="_method" type="hidden" value="DELETE"> <input name="_token" type="hidden" value="CLOKiLCPl2Ggr3IdbliPD14SLlXdBKgJzS33Hwoy"> <input class="btn btn-danger" type="submit" value="削除"> //2行目以降 <form method="POST" action="(url.....)/item/2" accept-charset="UTF-8"> <input name="_method" type="hidden" value="DELETE"> <input name="_token" type="hidden" value="CLOKiLCPl2Ggr3IdbliPD14SLlXdBKgJzS33Hwoy"> <input class="btn btn-danger" type="submit" value="削除"> </form>このように1行目だけ
<form></form>
で囲まれてない。。Qiitaで質問したところStackOverflowでも質問してる人がいたらしく、具体的な解決は見当たらず。。。
でも、他のところはFormファサードを使っているのでここも使いたい。。。。。解決策
最初で述べた通り。
@foreach ($items as $item) …… <td> <a href="{{ action('ItemController@show', [$item]) }}"> <button type="button" class="btn btn-primary">詳細</button> </a> <a href="{{ action('ItemController@edit', [$item]) }}"> <button type="button" class="btn btn-primary">編集</button> </a> <div style="display: inline-flex"> @if($loop->first) // 1行目だけ <form action=""></form> // 空のアクションを用意したformを用意する。 @endif // ifおわり {{ Form::open(['method' => 'delete', 'url' => action('ItemController@destroy', [$item])]) }} {{ Form::submit('削除', ['class' => 'btn btn-danger']) }} {{ Form::close() }} </div> </td> @endforeachとしたら、うまくレンダリングされました。
感想
結果として上手くいきましたが、htmlの知識は深いわけではないので、これで何か別の危険性が生まれたりしないか心配です。
こだわりのない人は、ファサードを使わずに<form></form>
で頑張るべき!!なにか、他の解決法、アドバイスがある方はコメントをお願いします。。
皆さんの参考になったら幸いです。
- 投稿日:2021-02-28T06:19:51+09:00
[Laravel7.x]Sanctumを用いたSPAで,未認証のユーザーが認証必須のAPIを叩いた場合ログイン画面に飛ばす
今回の題
認証にLaravel Sanctumを使ったSPAのサービスを作成していた際、未認証のユーザーが認証が必要なAPIを叩いたときは共通でログイン画面に飛ばしたかった。
※ axiosを使ったリクエストであることが前提です。より良い手段があればコメントにてお教えいただけますと助かります?♂️
方法
認証必須に設定したルートに対し未認証でリクエストを行うと、ステータスコード401のレスポンスが返るので、それをinterceptorsで捕まえて処理する。
resource/js/app.js// 諸々略 import './bootstrap' import router from './router' axios.interceptors.response.use(function (response) { return response; }, function (error) { if(error.response.status === 401) { router.push('login') } return Promise.reject(error); });リフレッシュを兼ねてページを再読み込みするのであれば、
router.push('login')
ではなく、window.location.href = "/login";のように遷移させてあげる。
以上!!参考
- 投稿日:2021-02-28T06:19:51+09:00
[Laravel7.x]Sanctumを用いたSPAで,未認証のユーザーが認証必須のAPIを叩いた場合,共通でログイン画面に飛ばす
今回の題
認証にLaravel Sanctumを使ったSPAのサービスを作成していた際、未認証のユーザーが認証が必要なAPIを叩いたときは共通でログイン画面に飛ばしたかった。
※ axiosを使ったリクエストであることが前提です。より良い手段があればコメントにてお教えいただけますと助かります?♂️
方法
認証必須に設定したルートに対し未認証でリクエストを行うと、ステータスコード401のレスポンスが返るので、それをinterceptorsで捕まえて処理する。
resource/js/app.js// 諸々略 import './bootstrap' import router from './router' axios.interceptors.response.use(function (response) { return response; }, function (error) { if(error.response.status === 401) { router.push('login') } return Promise.reject(error); });リフレッシュを兼ねてページを再読み込みするのであれば、
router.push('login')
ではなく、window.location.href = "/login";のように遷移させてあげる。
以上!!参考
- 投稿日:2021-02-28T03:20:09+09:00
Laravel8のdatetime型をjson出力するとYYYY-MM-DDT00:00:00.000000Z(ISO8601)で出力される
はじめに
モデルからdatetime型のカラムを取得し、jsonで出力すると
日付がYYYY-MM-DDT00:00:00.000000Z(ISO8601)で出力される。
調べてみるとlaravel7から変更された仕様とのこと。
■参考URL
https://laravel.com/docs/8.x/eloquent-serialization#date-serialization楽観排他チェックに更新日を使っているため、ISO8601では困る。
共通で日付を変換できるように修正する。対応方針
\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Concerns\HasAttributes.php/** * Prepare a date for array / JSON serialization. * * @param \DateTimeInterface $date * @return string */ protected function serializeDate(DateTimeInterface $date) { // return Carbon::instance($date)->toJSON(); return $date->format('Y-m-d H:i:s'); }よくはないがフレームワーク内を修正。
終わり
参考サイトではserializeDateをオーバーライドすると書いてあるが、
どうすればいいかわからず。
とりあえずの対処方法を残します。