クマのブログ

つまづいたところ、学びを書いていきます

【Laravel】Requestクラスについて

前提

背景

  • 業務中につまづいた点がRequestクラスの理解不足(というより、Requestという概念の忘却)が原因だったので、公式ドキュメントを見ながら再確認

  • 公式ドキュメントから個人的に「これ便利だな」「こんな使い方できるんだ」を抽出して書き出し

readouble.com

内容

基本的な使い方(依存注入)

  • クラスはIlluminate\Http\Requestにある

  • 基本的に初回アクセス時はGET送信、それ以降検索などをかけた際にはPOST送信が実行される

  • よくある使い方は以下のように依存注入(DI)を行ってRequestクラスへアクセスするパターン

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * 新しいユーザーを保存
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $name = $request->input('name');

    }
}
  • これでinputタグの内name属性が'name'である値を取得できる

$name = $request->input('name');

  • 以下のようにRuoteにRequestをタイプヒントして、Requestで受けとった値を取得することも可能
use Illuminate\Http\Request;

Route::get('/', function (Request $request) {
    //
});

依存注入+ルートパラメータ

  • 例えば、ユーザーごとに動的に出力内容を変更するページを実装する時、以下のようなルートになるはず。
use App\Http\Controllers\UserController;

Route::put('/user/{id}', [UserController::class, 'update']);
  • Controllerでは以下のように、メソッドの引数に$idとすることでメソッド内で$idを使える(Model内のidをfindしたり、、、)
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * 指定ユーザーを更新
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  string  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }
}

リクエスト受信側でできる事

リクエストパスの取得

$uri = $request->path(); 
  • 返り値:パス名

→例…routeがhttp://localhost.com/laravel/indexであればlaravel/indexが取得できる

リクエストパス/ルートの検査

$request->is('admin/*')
  • 返り値:boolean

→例…上記コードであればrouteがhttp://localhost.com/laravel/indexであればfalse, http://localhost.com/admin/~であればtrue

ここまでは超基本なので、別途アウトプットします。

次は実際に自分がつまづいた、直接メソッドの引数にDIできない場合のRequestへのアクセスをまとめます。

PHP-CS-fixerで手軽に読みやすいコードに。

前提

背景

  • プロジェクトのReadmeにあった、初めて見るツールPHP-CS-Fixerを使ってみて便利

  • 実務未経験の時の自分が知ってれば役に立ったな、と思ったのでアウトプット

PHP-CS-fixerとは?

github.com

何か?

  • The PHP Coding Standards Fixerの略

  • ツールをインストールし、実行するだけで簡単に可読性の高いコーディングに変更してくれる

  • PHPのコーディング規約であるPSRを基準にしてコードを変更してくれるので、汎用性の高いセルフコードレビューが可能

  • PHP用のコードエディタPHPStormにもデフォルトで使えるようになってる(但し、PHPStorm自体が有料)

  • ただし「リーダブルコード」とかにある一般的なコーディング規約を網羅しきれているかは保証できない。

あくまで基準はPSR。

www.php-fig.org

なぜやるのか?

「可読性」の正解はなかなかわからない…(特に独学中は)

PSRを自分で読み込んで可読性向上を狙うのは手間…

といったコストをグンと落とすことができ、圧倒的な時短につながる

実際に使ってみた

コチラを参考にさせて頂いた

qiita.com

1、インストール

composerが既にインストールされていることが前提

f:id:kuma_kuma0121:20210821145331p:plain

composer.jsonがあるディレクトリで以下コマンドを実行

composer require --dev friendsofphp/php-cs-fixer

わーっと色々インストールされるのを不安げに待ちながら…

↓インストールの確認

./vendor/bin/php-cs-fixer --version

↓結果

f:id:kuma_kuma0121:20210821145611p:plain

2、整形箇所の確認

整形を実行する前に、整形が必要な箇所を確認

 ./vendor/bin/php-cs-fixer fix --dry-run <ディレクトリ名>

↓結果 f:id:kuma_kuma0121:20210821150120p:plain

※記事内にある--diff-format udiffを使った差分の表示形式指定については、既にver3以降はformatはデフォルトでudiffになっているので対応不要。やってもエラー吐くだけ。

f:id:kuma_kuma0121:20210821150402p:plain

↓参考

github.com

github.com

3,整形の実行

整形される箇所を確認したらあとは実行するだけ。

./vendor/bin/php-cs-fixer fix <ディレクトリ名>

↓結果

f:id:kuma_kuma0121:20210821152639p:plain

ものの数秒でPSRに準拠したコーディングが実現。

さらに。。。

  • PSRに準拠するだけでは物足りない方にはオリジナルのコーディングルールを作って適用させることができるらしい。

  • これにより、個人用コーディングルール(.php_cs.dist)とプロジェクトチーム共有用(.php_cs)で使い分けることができる。

  • 「自分でコーディングルールの内容を作るのは面倒」という方は以下記事参照。

Githubから.php_cs.distをインストールすることができ、デフォルトの.php_csよりも精度の高いコーディングルールを使うことができる

PHP-CS-Fixerの設定が面倒くさい人のためのルールセット - Qiita

所感

  • 自力で可読性の高いコードを書くのは難しい中、最低限の可読性を担保してくれるのはありがたい。

  • 手間もかからず、可読性を意識したコーディングを書くことができるので、これからエンジニアを目指す独学者には使って頂きたいツールです!

【Laravel】モデルのデータ取得

前提

背景

  • 業務内で初めて知ったことのメモ

  • 思い込みが崩れた瞬間

事象(やりたいこと)

DB内のデータをモデル経由で取得したい

困ったこと

1, $user = User::get()->where('id', 1);
2, $user = User::where('id', 1);
  • 上記1も2も同じようにuserテーブルのidカラムが1のレコードを取得してきてくれると思っていたが違った

  • 実際にdd()してみると、結果は以下の通り。

1, のdd()結果

  • Illiminate\Database\Eloquent\Builderが返ってくる

    • 現状使いどころは不明…

2, のdd()結果

  • Illiminate\Database\Eloquent\Collectionが返ってくる

    • Modelについては実際に取得したかったuserテーブルのidが1であるレコードが取得できており、Modelのattributesに連想配列で格納されている

    • 値そのものの取得は今回であれば以下の感じ

// すべてのカラムの情報出力
foreach ($user as $val){
  echo $val
}

// nameカラムのみ取得
$user['name'];
  • Modelから値を取得したい時にやる王道パターン

所感

  • 1, のBuilderのパターンの使いどころは特にわかっていないので、今後使うことがあれば別途アウトプット

  • 一つ誤解が解けて良かった

【Laravel-admin】GridとWidgetの同時利用

前提

  • Windows 10

  • PHP 8.0

  • Laravel 8.0.2

  • Laravel-admin(ライブラリ)

背景

  • 業務中、Laravel-adminを使っており、試行錯誤した中でかなり時間を使ったので、メモ

  • Laravel-adminとは、WEBアプリで必須の「管理画面」を簡単に作ってくれるライブラリ

laravel-admin.org

事象(やりたいこと)

「Laravel-AdminのGridという機能と、Widget(Table)という機能を同じページ内で使いたい」

↓Grid

laravel-admin.org

Widget(Table)

laravel-admin.org

  • Grid:ユーザーリスト一覧ユーザー登録など管理画面で必要なCRUD機能が簡単に実装できる機能

  • Widget:上記GridのようなリストをBoxやinfoBox、Tableの形で表示する機能。今回はTableという機能。

困ったこと

この二つを同時に表示する方法がわからない

試したこと

1,demoサイトを確認

  • 公式マニュアルにdemoサイトと、Githubのコードがあったので、同じような使われ方していないか確認してみた

↓demoサイト

demo.laravel-admin.org

↓demoサイトGithub

github.com

結果

GridとTableを同時に表示させているdemoはなく、解決せず。

2,ソースコードを読み込んでみる

Grid

    protected function grid()
    {
        $grid = new Grid(new User());
        $grid->column('id', 'ID')->sortable();
        ~~~...
    
        return $grid;
    }
  • gridメソッドによってGridの中身を生成

  • $gridにGridクラスのインスタンスを生成し、用意されているcolumnなどのメソッドを使っている

引き数にはモデルやJsonを使ってデータベースなどから値を取得できる。

今回はUserモデルから値を取得

戻り値までに冒頭に生成したインスタンスの内容を設定していき、最後にreturnすることで画面を構成する、という流れ。

Widget(Table)

    public function table(Content $content)
    {
        $content->title('Table');
        $headers = ['Id', 'Email', 'Name', 'age', 'Company'];
        $rows = [
            [1, 'labore21@yahoo.com', 'Ms. Clotilde Gibson', 25, 'Goodwin-Watsica'],
            [2, 'omnis.in@hotmail.com', 'Allie Kuhic', 28, 'Murphy, Koepp and Morar'],
            [3, 'quia65@hotmail.com', 'Prof. Drew Heller', 35, 'Kihn LLC'],
            [4, 'xet@yahoo.com', 'William Koss', 20, 'Becker-Raynor'],
            [5, 'ipsa.aut@gmail.com', 'Ms. Antonietta Kozey Jr.', 41, 'MicroBist'],
        ];
        $table1 = new Widgets\Table($headers, $rows);
        $content->row((new Widgets\Box('Table-1', $table1))->style('info')->solid());
        ~~~...
        return $content;
}
  • Contentクラスを引数にしたtableメソッドを使い、メソッド内で$contentによりContentクラスを使用

  • $header, $rowsでそれぞれヘッダーと一覧の要素を生成

  • $header, $rowsを引数にして$table1でWidgetsクラス内にあるTableクラスをインスタンス化。

  • インスタンス化した$table1を引数にして、$content(Contentクラス)内のrowメソッドすることで、構成する画面の要素を格納

  • $contentをreturnすることで、画面の生成できる

着眼点

  • Gridを使うUserControllerはAdminControllerを継承しており、画面の出力はAdminControllerのindexメソッドで実行していると判明。
class AdminController extends Controller
{
~...
    public function index(Content $content)
    {
        return $content
            ->title($this->title())
            ->description($this->description['index'] ?? trans('admin.list'))
            ->body($this->grid());
    }
~...
}
  • ここにGridを表示する$this->grid()があることと、$contentがあることから

「このindexメソッドをUsersControllerでオーバーライドし、AdminControllerと同じように$contentをリターンすれば表示できるかも」

と推測。

  • 以下のように実装。
    // Contentクラスを引数にして、indexメソッドをオーバーライド
    public function index(Content $content)
    {
        /* Tableの生成*/
        // WidgetController同様、$contentの中身を生成
        ~...
        // Tableのインスタンス化
        $table = new Widgets\Table($headers, $rows);
        // ContentクラスのbodyメソッドでTable内容を設定
        $content->body((new Widgets\Box($select_timestamp, $table))->style('danger')->solid());

        /* Gridの生成 */
        // Gridクラスのインスタンス化
        $grid = new Grid(new Order());
        // Gridの中身生成
        $grid->column('id', __('ID'));
        ~...
        // $grid内容をbodyに格納
        $content->body($grid);
        // $content(Contentクラス)を返す
        return $content;
}

結果

無事表示させれました。

所感

「関連するソースコードは深層までクラスやメソッド、プロパティを確認しすべての流れを理解せねばいけない」

と思っていた

  • 実務の業務の中で大切なのは

    • 「どこに(どのファイルに)、何が(クラスなのか、単一な値なのか)、どんな形で(配列か、数値か、文字列か)返ってくるか」を意識

    • 「こうなるかも?」と想像を働かせながらパズルを組むようにソースコードを見て、トライ&エラ―を繰り返して答えを見つけていく

この工程だなと実感。

  • もちろん、深層まで見ることもあるが、それよりも普段大切なのは「~したら、~になるかもな」という推察力。

今回はこれに気づけた事象でした。

【PHP】for文を使った配列の処理

前提

背景

  • 会社の研修中、PHPの基礎講座を受講

  • 今の会社に就職前に独学でPHPを学習していたが、初めて知ったことがあったので、アウトプット

事象(やりたいこと)

以下2重配列をデータベースにINSERTしたい

↓配列:$_SESSION['cartinfo']

array(3) { [0] => array(3) { 'isbn' => string(4) "0018" 'title' => string(4) "java" 'price' => string(3) "700" } }
array(3) { [1] => array(3) { 'isbn' => string(4) "0019" 'title' => string(7) "android" 'price' => string(3) "500" } }
array(3) { [2] => array(3) { 'isbn' => string(4) "0020" 'title' => string(3) "php" 'price' => string(3) "600" } }

困ったこと

  • 当初はfor文を使って、以下のようにINSERTしていた
for($i=0; $i > $cartinfoCnt; $i++){
  $sql_insert = "INSERT INTO orderinfo VALUES(NULL, '{$userName}', '{$_SESSION['cartinfo'][$i]['isbn']}', {$quantity}, '{$date}')";
  $result_insert = executeQuery($sql_insert);
}
  • ただ、プログラム内で$_SESSION['cartinfo']内のデータを消す機能も実装されていた。

そうなると、例えば添え字が[1]のデータが消した場合、for文では[0]のデータを処理した後、「次のデータ[1]がないよ!」とエラーが吐かれる

試したこと

1, forではなくforeachに変更

  • 以下のようにforeachで実装し直した
foreach($_SESSION['cartinfo'] as $key => $val){
  $isbn = $val['isbn'];
  $sql_insert = "INSERT INTO orderinfo VALUES(NULL, '{$userName}', '{$isbn}', {$quantity}, '{$date}')";
  $result_insert = executeQuery($sql_insert);   
}

結果

これで途中の[1]が消えても問題なく[0], [2]...と処理ができるようになった

所感

  • 配列であればforeachを使うのが通常だと思う。

では、なぜ私が当初forを使ってしまったのか。

要因は配列にある添え字([0], [1], [2]...)を見て、「数字で順番に回さなきゃいけない!」と直感的におもったため。

  • 今回のforeachへの置き換えにより、foreachは「添え字に関係なく上から順にデータがなくなるまで処理を繰り返す」という特性を持っていることを改めて学ぶことができた

【PHP】目に見えないエラー対応

前提

背景

  • 会社の研修中、PHPの基礎講座を受講

  • 今の会社に就職前に独学でPHPを学習していたが、初めて知ったことがあったので、アウトプット

事象(やりたいこと)

  • $_SESSION['cartinfo']内にあるデータを繰り返し処理したい
 // 整数値3を返す
 $cartinfoCnt = count($_SESSION['cartinfo']);

 // 繰り返し処理
 for($i=0; $i > $cartinfoCnt; $i++){
  $sql_insert = "INSERT INTO orderinfo 
VALUES(NULL, '{$userName}', '{$_SESSION['cartinfo'][$i]['isbn']}', {$quantity}, '{$date}')";
  $result_insert = executeQuery($sql_insert);
}

困ったこと

繰り返し処理ができず、$_SESSION['cartinfo']内のデータが表示されない

試したこと

1処理がfor文に入っているか調べる

for($i=0; $i > $cartinfoCnt; $i++){
  echo "aaa";
  $sql_insert = "INSERT INTO orderinfo VALUES(NULL, '{$userName}', '{$_SESSION['cartinfo'][$i]['isbn']}', {$quantity}, '{$date}')";
  $result_insert = executeQuery($sql_insert);
}
  • 上記の通りechoを入れてみて、挙動を確認

結果

"aaa"が出力されず、for文を通っていないことを確認

2. for文を通らない原因を探る

  • for文を1文づつ確認し、通らない原因を探る

結果

  • 原因は不等号が逆であったこと

  • 以下のように修正したら無事for文を通った

↓修正前

for($i=0; $i > $cartinfoCnt; $i++){

↓修正後

for($i=0; $i < $cartinfoCnt; $i++){

所感

  • 以下にエラー対応がすぐにできなかった要因を推測

    • 1.「for文はif文と比べて絶対に通ってくれるだろう」という固定概念を持っており、「for文を通っているか否かのデバックを試す」という方法がすぐに思い浮かばなかった。

      1. 完全に言い訳ですが、同時並行でやっていた基本情報技術者試験で出題される疑似言語では「反復条件」ではなく「終了条件」を冒頭に書くものも存在しており、混乱していた
      1. 根拠もなく「SQL及びデータベース構造に非があるのではないか」と判断したため、労力を真因以外に注力してしまった。

SQL文のエラーを検出しdieする処理を別ファイルで用意しており、そちらでエラーは発生していなかった。

よって本来はSQL文及びデータベースの調査は不要でした。

結論:今回の時間の浪費の真因は3.で、想定外の挙動に対して冷静な真因調査ができなかったことです。 エラー文が出ない想定外の挙動についてもエラー文が表示されたとき同様、冷静にデバックを実施することで、今回の事態は避けられたのではないか、と感じた。

mysqli_num_rowsの使い方

前提

背景

  • 会社の研修中、PHPの基礎講座を受講

  • 今の会社に就職前に独学でPHPを学習していたが、初めて知ったことがあったので、アウトプット

事象(やりたいこと)

mysqli_num_rowsで出たエラーを解消したい

  /* DB接続済  */
 // SQL文の設定
 $sql_select="INSERT INTO bookinfo VALUES('{$isbn}', '{$title}', {$price})";

 //クエリの実行
 $result_select = executeQuery($sql_select); 

 // 取得したクエリデータ数のカウント
  $rows = mysqli_num_rows($result_select);

  ~~~

  if($rows > 0){
 
  // 異常系。SQL実行結果がDB内のデータと重複した場合の処理

  }else{

  // 正常系。SQL実行結果がDB内のデータと重複せずユニークである場合の処理

  }

困ったこと

以下エラーが発生

Warning: mysqli_num_rows() expects parameter 1 to ~...

試したこと

1. INSERT処理と重複チェック処理を分けて考えた

  • 上記ソースコードは「全ての既存データをチェックする処理」と「INSERTする処理」をごっちゃにしていたため、起きた事象です。

  • ソースコード上の原因は$sqlがINSERTであるにも関わらず、mysqli_num_rows関数を使って処理しようとしたところでエラーが発生していました。

  • 最終的に冒頭述べた通り、以下2つの関数を作り挙動を確認

    • SELECT文によるチェック機能を持つ関数

    • INSERTするだけの関数

結果

エラーが発生せず、正常に動くようになった

所感

  • 初めて見たエラーでしたが、落ちついて処理を追っていけば対処できるエラーだと認識

  • mysqli_num_rowsは引数の行数をintで返すので、今回の場合、感覚的に挿入する1行分を読み取り「1」が返ってきそうなものですが、実際は何も返ってこなかった

感覚的な挙動を試し、実際の挙動を見て答え合わせできた経験は貴重だったし、印象に残った。