20210228のlaravelに関する記事は15件です。

Laravel ファイル名が日本語の添付ファイルが送れなかった

はじめに

あるプロジェクトでファイル名が日本語のPDFをメールで送信する必要がありました。
公式や記事を見ながら実装したところ、何故か日本語部分だけが送信できないという事象が発生しました。

前提

Centos7系
PHP7系

LaravelのMailファサード・Mailableクラスを用いて実装しています。

HogeController.php
namespace 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

調査

以下の順序で調査を行いました。

  1. config.phpのlocaleがjaになっているか
  2. 参考記事のようにファイル名を参照する際にbasename()を使用しているかどうか
  3. サーバーのlocaleが正しいか
  4. 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.php
public function sendMail()
{
    Mail::to('hogehoge@hogehoge')->locale('ja')->send(new SampleMailable());
}

今回の対処法

大人の事情でサーバー上にあるphp.iniを編集することは叶いませんでしたので、以下の内容で対応しました。

AppServiceProvoder.php
class 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.ini
mbstring.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

これでもうまくいくと思います。(未検証なので、ご存知の方がいらっしゃいましたら教えていただけると幸いです!)

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

シーダー実行に失敗したため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]:が発生しました。

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

(備忘録)PHPでデプロイとアプリ公開まで

参考記事

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

【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>
@endforeach

bladeでそのままデータを表示する場合は、上記のようにすれば問題ない。
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>
@endforeach

keyを追加することでユニークなidをid=myBarChart0id=myBarChart1id=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.php
var 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以下に複数の配列が存在する以下のような場合、
スクリーンショット 2021-02-28 19.20.54.png

それぞれの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で表示することができる。
スクリーンショット 2021-02-28 19.42.41.png

おまけ

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に限らず多くのライブラリで同じような方法で個別に生成したりできるので、他のライブラリでも挑戦してみてください。

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

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をする必要があるんそうです。
これで解決!!

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

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ファイルに記されているデータベース設定を必要に応じて修正する

.env
DB_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.php
public 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.php
use 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.php
if ($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.php
if ($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.php
    public 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-28 17.56.01.png

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

【Laravel】route関数を使用する場合のSSL化対策

SSL化する前の状態

bladeテンプレートのformのaction属性をroute関数で記載。
route関数についてはこちらの記事で説明しています。

<form method="post" action="{{ route('profile.create') }}">

上記の記述だと、URLがhttp://・・・になってしまいます。
SSL化されていませんので遷移先の画面では遷移先では下記画面が表示されます。
bladeエラー.png

ちなみに、「このまま送信」ボタンをクリックするとエラーになります。
エラーの原因は、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化した時にやったことと参考記事一覧

参考

LaravelのSSL化対策

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

【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.envSESSION_DRIVERの値はどちらもfileを指定していたのですが、Dockerfileの環境変数の設定でSESSION_DRIVERcookieを指定していたため、反映されずに詰まってました。

引用

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

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 selenium

Dusk導入まで

やり方は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.conf
    location ~ ^/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)

動いたー! やったー!

まとめ

過去の知見となんとか駆使すればなんとかなるということです、はい。

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

PHP8(Laravel8)でデスクトップアプリケーションを作る方法

今回はPHP8,Laravel8にてデスクトップアプリケーションを作る方法を紹介します。

phpdesktop( https://github.com/cztomczak/phpdesktop )を使うことによってWEB技術によってデスクトップアプリケーションを作ることが可能になります。

早速、起動するまでの手順を書いていきます。
ちなみに自分はwindows環境です。

phpdesktopをダウンロードする

githubのreadmeにOSごとの最新バージョンのReleaseへのリンクがあるので自分の環境に合わせたものをダウンロードして解凍します。

キャプチャ.PNG

中身はこんな感じ

キャプチャ2.PNG

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/laravel

laravelフォルダに行き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を実行すると

キャプチャ3.PNG

起動できました!!

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

【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のみ。影響範囲が小さく済みます。

参考

Laravel 7.x ルーティング

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

[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で表示していて、その行の右端にアクションボタンとして、詳細、編集、削除ボタンを用意しています。
こうすると,
pic1.png
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>

pic2.png
どうした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>で頑張るべき!!

なにか、他の解決法、アドバイスがある方はコメントをお願いします。。

皆さんの参考になったら幸いです。

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

[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";

のように遷移させてあげる。
以上!!

参考

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

[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";

のように遷移させてあげる。
以上!!

参考

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

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をオーバーライドすると書いてあるが、
どうすればいいかわからず。
とりあえずの対処方法を残します。

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