20200714のPHPに関する記事は10件です。

【学習記録(PHP)】ハッシュ化のいろいろ

日々の学習を忘れないように記録をつけています。

ハッシュ関数って?


ハッシュ関数は、「ある引数を渡すしたら、でたらめな値を返す関数」です。
このとき、返される値をハッシュ値と呼びます
返ってくる値はでたらめですが、同じ値を渡すと必ずそれに対応した
同じ値が返ってくるという特徴があります。
ハッシュ化には、hash関数、md5関数、sha1関数、password_hash関数がよく用いられます。



暗号化とすこし似ていますが、仕組みは異なるようです。
ハッシュ化は不可逆な変換で、元のデータへの復元ができないのに対して、
暗号化は適切な鍵を使うことで元データを復元することができます。

sha1関数

sha1関数は、16進数・40文字のハッシュ値を生成するハッシュ関数です。

qiita shal1.php
$str = "yahhooo";
$number = sha1($str);

第一引数で入力文字列、
第二引数で返り値の形式を指定できます。

sha1の脆弱性

sha1は便利な関数である反面、脆弱性が知られています。
2017年、googleは2つの異なるファイルを使って同じハッシュ値の生成する
“衝突実験”に成功しました。
sha1衝突が悪用されれば、ハッシュ値に依存するシステムで、
ファイルを不正なものに入れ替えられる可能性があるとのこと。
現在ではよりハッシュ値の長いsha2の利用が推奨されています。

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

Class env does not exist in... の原因を一瞬で突き止める方法

わりとハマったのでメモを残します。

環境

  • Laravel 5.8
  • PHP 7.1

方法

App\Exceptions\Handler::report()内にdd($exception)を追記し、ダンプしたとこで処理を止めてエラーの内容を確認する。

Class env does not exist in...

Laravelを使っていると、ごく稀に以下のような感じで "Class env does not exist in..." というエラーに遭遇します。
このエラーが出ると、artisanコマンドもcomposerコマンドも動かないし、何をやっても "Class env does not exist" というエラーメッセージが出力されるだけの状態になり、結構面倒です。

// こんな感じのエラー。滅多にないが、すごくたまに出る

Fatal error: Uncaught ReflectionException: Class env does not exist in /var/www/html/vendor/laravel/framework/src/Illuminate/Container/Container.php on line 788

ReflectionException: Class env does not exist in /var/www/html/vendor/laravel/framework/src/Illuminate/Container/Container.php on line 788

Call Stack:
    以下スタックトレース...

なおかつこのエラーはenvが無いことが原因でもなければ、よくあるcomposer周りのミスが原因でもない場合が結構あるので、エラーメッセージを額面通り受け取ると、本来不具合がある部分に気づけないまま時間を溶かす恐れがあります。

dd()で本来のエラー原因を探す

「envが無いとか言われたけどenvはある。たぶん何かやらかしたんだろうけど、こんなエラーを引き起こすようなことをした覚えはないし、心当たりもない」
みたいな時は、一旦"Class env does not exist..."から離れてもっとマシなエラーメッセージが得られるようにすれば、解決に近づくことがあります。

app/Exceptions/Handler.php
public function report(Exception $exception)
{
    dd($exception) //<-この1行を追加
    parent::report($exception);
}

これでダンプされた時点で処理が止まり、もっとマシなエラーメッセージとスタックトレースが確認できるようになります。
これで得られたエラーメッセージをもとに探ってゆけば、割と楽に解決にたどり着けるんじゃないかなと思います。

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

PHP トレイトとは

トレイト

class内の中にclassを追加してそのメゾットなどを再利用することができる

サンプルコード.

sample.php
<?php

class Car
{
 public function run()
 {
 echo "running by Car\n";
 }

 public function horn()
 {
 echo "beeeep!! by Car\n";
 }
}

trait Honda
{
 public function run()
 {
 parent::run();
 parent::horn();
 echo "running!! by Honda\n";
 }
}

class Machine extends Car
{
 use Honda;
}

$machine = new Machine();
$machine->run();

# running by Car
# beeeep!! by Car
# running!! by Honda



参考

https://www.php.net/manual/ja/language.oop5.traits.php

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

PHP - 偶数or奇数を判定する方法

対象の数字が偶数なのか、奇数なのか、if文で判定します。

・パターン1
対象の数字($A)を2で割った時、余りがないかを確認しています。
余りがない場合は「偶数」です。

<?php
if($A % 2 == 0){
<偶数の場合>
}else{
<奇数の場合>
}
?>

ビット演算子を使用します。
ビットは2進数なので、2で繰り上がっていきます。
なので、2で割り切れない数字(奇数)は2進数化した10進数の末尾が1となります。

<?php
if($A & 1){
<奇数の場合>
}else{
<偶数の場合>
}
?>

◼︎用語の詳細
・A % B
代数演算子。剰余。AをBで割った時の余り。

・A & B
ビット演算子。ビット積。

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

ブラウザの戻るボタンを無効化する

javascriptでブラウザの履歴を操作する

やりたかったことは、

一日一度押せるボタンがあって押したらページ遷移して、遷移先のページから戻るボタンでは前のページに戻らせない(ブラウザバックで戻るとサイドボタンを押せてしまうため)

window.addEventListener('DOMContentLoaded', function () {
  // 戻るボタンを制御
  history.pushState(null, null, location.href);
  window.addEventListener('popstate', (e) => {
    history.go(1);
  });
});

history.pushState(null, null, location.href);

まずこの記述で、偽の履歴を追加しています。

window.addEventListener('popstate', function(e){...}

ブラウザバックのイベントを取得して

history.go(1);

で偽装したページに遷移させます。

以上です。

参考にさせてい頂いたサイト
https://pisuke-code.com/javascript-prohibit-browser-back/
ありがとうございました。

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

本番運用しているサーバーにgit/GitHubを導入するときのベストプラクティスとマサカリ

本番運用しているphp/Laravelサーバーにgitを入れたいんだけどこれであってるのかな。

こんにちは! @ykhirao です!本番サーバーにFTPでぽちぽちアップロードしている現状ですが、 ssh production git pull origin master とかで本番コードを反映させたいなと夢みたいなことを考えています。適当に考えたベストプラクティス(たたき台ともいう)を投稿するのでマサカリください!!!!!

手順の大まかなながれ

だいたいこんな状況を仮定すると

  • ローカルブランチにgitを導入して、GitHubにpushする
  • GitHubのレポジトリがあるし、masterブランチで最新になっている
  • 本番コードはだいたいmasterブランチと同じ、一部デプロイ忘れとかある

こんなものかなと思う。それに対してやることは

  • サーバー上で ssh key-gen して公開鍵・秘密鍵を作成する
  • GitHubでデプロイキーを登録する(GitHubにデプロイユーザーとか作らなくていいよ!)
  • .gitignoreが適切に設定されているか確認する
  • 特に.envファイルとか、storageフォルダとか、DB以外に本番とローカル環境で違うものがないかよく確認する

とかでしょうか。

実際の動作で確認する

GitHubにレポジトリ作るのを省略するためにlocalにremote addする。
GitHubとして読み解いてほしい。

yk@yk local % git remote add origin /Users/yk/server/project.git
yk@yk local % cd ..                 
yk@yk server % ls
local   prod
yk@yk server % git clone --bare local project.git 
Cloning into bare repository 'project.git'...
done.
yk@yk server % cd local 
yk@yk local % git push origin master                            
Everything up-to-date

prodフォルダにcloneしてくる!!で .git フォルダを削除して、git管理下に置かれてない状況に戻す。

yk@yk server % cd prod
yk@yk prod % ls
yk@yk prod % git clone /Users/yk/server/project.git .
Cloning into '.'...
done.
yk@yk prod % ll
total 8
drwxr-xr-x   5 yk  staff  160  7 11 01:54 .
drwxr-xr-x   5 yk  staff  160  7 11 01:51 ..
drwxr-xr-x  12 yk  staff  384  7 11 01:54 .git
-rw-r--r--   1 yk  staff   16  7 11 01:54 .gitignore
-rw-r--r--   1 yk  staff    0  7 11 01:54 README.md

yk@yk prod % rm -fr .git

# 本番ではstorageフォルダに画像がたくさん溜まっていることを仮定
yk@yk prod % mkdir storage
yk@yk prod % touch storage/image-prod.jpg
yk@yk prod % touch storage/image-prod2.jpg
yk@yk prod % touch storage/image-prod3.jpg

# .envファイル を本番用に書き換え
yk@yk prod % vim .env
yk@yk prod % cat .env 
APP=prod

いろいろlocalで作業した!

yk@yk server % cd local 

yk@yk local % echo "# README.md を追記" >> README.md 

yk@yk local % git diff
diff --git a/README.md b/README.md
index e69de29..7039b84 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1 @@
+# README.md を追記

yk@yk local % git commit -am "Update"
[master 1b9e11d] Update
 1 file changed, 1 insertion(+)

yk@yk local % git push origin master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 298 bytes | 298.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yk/server/project.git
   cf9a63e..1b9e11d  master -> master

さてこの状況で本番にgitを導入しようと思います。

  • 少しlocalのほうが進んでいる
  • .envファイルが書き換わるとまずい
  • storage配下に画像たくさんあるけど.gitignoreしているから大丈夫なんだっけ?

とかそんな感じの疑問を抱えつつ、見てみます。

サーバーで ssh-keygen をして 公開鍵を GitHubのこのあたりhttps://github.com/XXXXX/XXXXX/settings/keys に登録すると、privateレポジトリでも、いい感じにpullとかできるようになります。デプロイユーザーとかをGitHubに登録しなくても大丈夫です。

本番のプロジェクトにはgit initされていない状態を確認する。

yk@yk prod % git log
fatal: not a git repository (or any of the parent directories): .git
yk@yk prod % git init 
Initialized empty Git repository in /Users/yk/server/prod/.git/
yk@yk prod % git log
fatal: your current branch 'master' does not have any commits yet

masterブランチもないし、操作できない状態なので、remoteでGitHubとかを指定して、pullしてきましょう。

yk@yk prod % git remote add origin /Users/yk/server/project.git 
yk@yk prod % git pull origin master
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 7 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (7/7), 516 bytes | 103.00 KiB/s, done.
From /Users/yk/server/project
 * branch            master     -> FETCH_HEAD
 * [new branch]      master     -> origin/master
error: The following untracked working tree files would be overwritten by merge:
    .gitignore
    README.md
Please move or remove them before you merge.
Aborting

いい感じ。

yk@yk prod % git log
fatal: your current branch 'master' does not have any commits yet
yk@yk prod % git status                   
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    .gitignore
    README.md

nothing added to commit but untracked files present (use "git add" to track)

まだlogも見れないのでブランチを切り替えて一旦コミットする

yk@yk prod % git checkout -b master-backup
Switched to a new branch 'master-backup'
yk@yk prod % git add .
yk@yk prod % git commit -m "マスターのバックアップ"
[master-backup (root-commit) a6fd38c] マスターのバックアップ
 2 files changed, 3 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 README.md

yk@yk prod % git branch
* master-backup

大丈夫そう。masterにチェックアウトしてみる

yk@yk prod % git checkout master
Branch 'master' set up to track remote branch 'master' from 'origin'.
Switched to a new branch 'master'

yk@yk prod % git log
commit 1b9e11d15bda2ecdec75a8d205bdbdec185a9388 (HEAD -> master, origin/master)
Author: Yuki <yuki.dees39th@gmail.com>
Date:   Sat Jul 11 02:01:05 2020 +0900

    Update

commit cf9a63ebb4676faa949aa29a9fdf56244ed313d0
Author: Yuki <yuki.dees39th@gmail.com>
Date:   Sat Jul 11 01:48:36 2020 +0900

    init

Update という変更はまだ prod フォルダには適応させてなかったのに、きちんとpullされていることがわかりますね!
これで最新ブランチの適応はOKかと思います。

yk@yk prod % cat .env 
APP=prod
yk@yk prod % ls storage 
image-prod.jpg  image-prod2.jpg image-prod3.jpg

.env ファイルとか storege/ とか .gitignore に記述しているものは書き換わってないことがわかります。
もちろんlocalファイルは AA=local ときちんとなったままです。

yk@yk prod % cat ../local/.env 
APP=local

まとめ

こんな感じでサーバー上で git init して remote add checkout -b git add . git commit git checkout master って流れでいい感じにgit適応できる気がしたのです。

あまりこのあたりちゃんとまとめられてなかったので、サーバー運用得意な方いたら編集リクエスト、
コメント欄記入、自分なりのやつを投稿、何でも構いませんので後世に残せるいいドキュメントになればと思ってます。

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

Laravel ルートパラメータ持ちのURLにリダイレクトする

目的

  • ルートパラメータが設定されているURLにパラメータ値を指定してリダイレクトする方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3 Homwbrewを用いて導入
Laravel バージョン 7.0.8 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

前提条件

  • 実施環境と同じ環境が構築されていること。

前提情報

  • 公式ドキュメントを読んでおくと理解が早いかもしれない。
  • /user/{user_id}にリダイレクトする方法を記載する。
  • ルーティングには下記の様な記載があるとする。ルーティング設定部分のみ記載する。

    web.php
    Route::get('/user/{user_id}', 'UserHomeController@index');
    

読後感

  • ルートパラメータが設定されているURLへのリダイレクト方法が分かる。
  • 名前付きルーティングにパラメータを渡す方法が分かる。

実施(一つのルートパラメータ)

  1. ルーティングファイルを開きリダイレクト先のルーティングの記載を編集し、ルーティング情報に名前をつける。

    web.php
    Route::get('/user/{user_id}', 'UserHomeController@index')->name('user.index');
    
  2. リダイレクト処理を行いたい場所に下記の記載を行う。(下記が実行されると/user/1にリダイレクトする。)

    return redirect(route('user.index', [
        'user_id' => 1,
    ]));
    

実施(複数のルートパラメータ)

  1. ルーティングファイルを開きリダイレクト先のルーティングの記載を編集し、ルーティング情報に名前をつける。

    web.php
    Route::get('/user/{user_id}/{age_id}', 'UserHomeController@index')->name('user.index');
    
  2. リダイレクト処理を行いたい場所に下記の記載を行う。(下記が実行されると/user/1/2にリダイレクトする。)

    return redirect(route('user.index', [
        'user_id' => 1,
        'age_id' => 2,
    ]));
    

簡単なまとめ

  • ルーティング情報に名前をつける方法

    //ルーティング情報->name('ルーティング情報の名前')
    Route::get('/user/{user_id}/{age_id}', 'UserHomeController@index')->name('user.index');
    //上記の記載でルーティング情報に「user.index」という名前が付けられた。
    
  • 名前付きのルーティング情報にリダイレクトする方法

    return redirect('ルーティング情報に付けた名前');
    
  • リダイレクト先のURLにルートパラメータがあるときにリダイレクトする方法

    return redirect('ルーティング情報に付けた名前', [
        'ルートパラメータ' => リダイレクト時にルートパラメータに入る値,
    );
    

参考文献

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

【Laravel】実行されたDBクエリの確認ができるやつを書いた

はじめに

自分はここ最近Laravelを使ったお仕事をしているのですが、こういった悩みがありました。

  • QueryBuilder便利だけど、実行されるクエリが全くわからん
    • レビューする側だと共感していただけるのでは??
  • 狙ったクエリが書かれているのか自信が無い
  • このメソッド叩くと無駄なクエリが流れるんだが
  • etc

今までは、PHPUnitでseederなどでデータを用意してクエリを実行するメソッド書いてアサーションして、お、期待するレコード取れたよね、やったやった!!で済ましていたのですが、いちいち条件網羅するデータを用意するのが難儀だったりします。
(実行されるクエリだけ分かればいいケースも中にはあると思うのです)

くぅ、実行されるクエリだけ見たい、見せたい、それさえ出来れば、、そんな悩みを抱えていたのであります。1
実行されるクエリの確認をなるべく楽にサクッとできるものが欲しい!! というわけで、

Laravelで実行されたDBクエリの確認ができるやつを書いてみました。2
(よく分からない前フリですいません。。)

何を解決するか

Laqu(ラク?レイク?) は、

  • 期待するクエリが流れているかPHPUnitでアサーション可能です
    • traitを用意しています
  • クエリが流れるメソッドを渡すと、どのようなクエリが流れるか確認することができます
    • 実行されたクエリの中から一番早かったもの、一番遅かったものが抽出できます
    • ソートも可能です
  • ビルド後(パラメータがバインド済み)のクエリが確認できます

を行うことができます。実行時間のところはBasic Database Usage - Laravel - The PHP Framework For Web Artisansで出された値(time)を利用しているので、そこまで当てにならないかもしれませんが実行されたクエリの確認は行なえます。

なお、開発中に利用されることを想定していますのであしからずです。

要件

  • PHP7.2 以上
  • Laravel 5.8.x, 6.x, 7.x

とりあえずなるべく最新のLaravelが動く環境であればOKだと思います。

インストール

composerを使います。

$ composer require --dev shimabox/laqu

使い方

QueryAssertion

期待するクエリが流れているかPHPUnitでアサーションするためのものです。
traitです。assertQuery()を使います。

<?php

use Laqu\QueryAssertion;
use Tests\TestCase;

class QueryAssertionTest extends TestCase
{
    use QueryAssertion;

    private $exampleRepository;

    protected function setUp(): void
    {
        parent::setUp();

        $this->exampleRepository = new ExampleRepository(); // 仮
    }

    public function queryTest()
    {
        // 基本的な使い方
        $this->assertQuery(
            // クエリが実行される処理をクロージャに渡します
            function () {
                $this->exampleRepository->findById('a123');
            },
            // 期待するクエリを書きます
            'select from user where id = ? and is_active = ?',
            // バインドされる値を配列で定義
            // (bindするものがない場合は空配列を渡すか、引数は渡さないでOK)
            [
                'a123',
                1,
            ]
        );

        // 複数のクエリを確認
        // 基本的には 1メソッド1クエリの確認 を推奨しますが、中には1メソッドで複数クエリが流れる場合も
        // あると思います。
        // その場合は下記のようにクエリとバインド値を配列で対になるように定義してください。
        $this->assertQuery(
            function () {
                // 例えばこの処理で複数のクエリが流れるとします
                $this->exampleRepository->findAll();
            },
            // 期待するクエリをそれぞれ配列で定義
            [
                'select from user where is_active = ?', // ※1
                'select from admin_user where id = ? and is_active = ?', // ※2
                'select from something', // ※3
            ],
            // バインドされる値を二次元配列で定義(bindするものがない場合は空配列を渡してください)
            [
                [ // ※1.
                    1,
                ],
                [ // ※2.
                    'b123',
                    1,
                ],
                // ※3 はバインド無しの想定なので空配列を渡します
                [],
            ]
        );
    }
}

こんな感じで、クエリのアサーションが行えます。

QueryAnalyzer

クエリが流れるメソッドを渡して、どのようなクエリが流れたのか確認することができます。
QueryAnalyzer::analyze() で実行されたクエリの結果(Laqu\Analyzer\QueryList)が取得できます。
※ QueryListの中身はLaqu\Analyzer\Queryです

<?php

use Laqu\Facades\QueryAnalyzer;

/** @var Laqu\Analyzer\QueryList */
$analyzed = QueryAnalyzer::analyze(function () { // クエリが実行される処理をクロージャに渡します
    $author = Author::find(1);
    $author->delete();
});

/*
Laqu\Analyzer\QueryList {#345
  -queries: array:2 [
    0 => Laqu\Analyzer\Query {#344
      -query: "select * from "authors" where "authors"."id" = ? limit 1"
      -bindings: array:1 [
        0 => 1
      ]
      -time: 0.08
      -buildedQuery: "select * from "authors" where "authors"."id" = 1 limit 1"
    }
    1 => Laqu\Analyzer\Query {#337
      -query: "delete from "authors" where "id" = ?"
      -bindings: array:1 [
        0 => "1"
      ]
      -time: 0.03
      -buildedQuery: "delete from "authors" where "id" = '1'"
    }
  ]
}
*/
dump($analyzed);

この取得結果をもとに

一番実行時間が早かったクエリの抽出 extractFastestQuery()

/*
Laqu\Analyzer\Query {#337
  -query: "delete from "authors" where "id" = ?"
  -bindings: array:1 [
    0 => "1"
  ]
  -time: 0.03
  -buildedQuery: "delete from "authors" where "id" = '1'"
}
*/
dump($analyzed->extractFastestQuery());

一番実行時間が遅かったクエリの抽出 extractSlowestQuery()

/*
Laqu\Analyzer\Query {#344
  -query: "select * from "authors" where "authors"."id" = ? limit 1"
  -bindings: array:1 [
    0 => 1
  ]
  -time: 0.08
  -buildedQuery: "select * from "authors" where "authors"."id" = 1 limit 1"
}
*/
dump($analyzed->extractSlowestQuery());

クエリの実行時間でソート sortByFast(), sortBySlow()

/*
array:2 [
  0 => Laqu\Analyzer\Query {#337
    -query: "delete from "authors" where "id" = ?"
    -bindings: array:1 [
      0 => "1"
    ]
    -time: 0.03
    -buildedQuery: "delete from "authors" where "id" = '1'"
  }
  1 => Laqu\Analyzer\Query {#344
    -query: "select * from "authors" where "authors"."id" = ? limit 1"
    -bindings: array:1 [
      0 => 1
    ]
    -time: 0.08
    -buildedQuery: "select * from "authors" where "authors"."id" = 1 limit 1"
  }
]
*/
dump($analyzed->sortByFast());

/*
array:2 [
  0 => Laqu\Analyzer\Query {#344
    -query: "select * from "authors" where "authors"."id" = ? limit 1"
    -bindings: array:1 [
      0 => 1
    ]
    -time: 0.08
    -buildedQuery: "select * from "authors" where "authors"."id" = 1 limit 1"
  }
  1 => Laqu\Analyzer\Query {#337
    -query: "delete from "authors" where "id" = ?"
    -bindings: array:1 [
      0 => "1"
    ]
    -time: 0.02
    -buildedQuery: "delete from "authors" where "id" = '1'"
  }
]
*/
dump($analyzed->sortBySlow());

ビルド後のクエリ取得 getBuildedQuery()

Laqu\Analyzer\QueryListから要素を特定して利用します。
※ QueryListは配列として扱えます

// select * from "authors" where "authors"."id" = 1 limit 1
echo $analyzed[0]->getBuildedQuery();
// delete from "authors" where "id" = '1'
echo $analyzed[1]->getBuildedQuery();

が利用できます。

Helper

ヘルパーとして以下機能も提供しています。

QueryLog

QueryLogはBasic Database Usage - Laravel - The PHP Framework For Web Artisansの処理をラップしたものです。

QueryLog::getQueryLog()

<?php

use Laqu\Facades\QueryLog;

$queryLog = QueryLog::getQueryLog(function () {
    Author::find(1);
});

/*
array:1 [
  0 => array:3 [
    "query" => "select * from "authors" where "authors"."id" = ? limit 1"
    "bindings" => array:1 [
      0 => 1
    ]
    "time" => 0.12
  ]
]
*/
dump($queryLog);

察している人は察していると思いますが、他のメソッドはこの機能をフル活用しています。
そのため、実行時間関連はそこまで精密ではありません。

QueryHelper

クエリとバインドパラメータを渡すと、実行されるクエリの確認ができます。
pdo-debug/pdo-debug.php at master · panique/pdo-debug を利用しています

QueryHelper::buildedQuery()

<?php

use Laqu\Facades\QueryHelper;

$now  = Carbon::now();
$from = $now->copy()->subDay();
$to   = $now->copy()->addDay();

$query = 'select * from authors where id in (?, ?) and name like :name and updated_at between ? and ?';

$bindings = [
    1,
    2,
    '%Shakespeare',
    $from,
    $to,
];

$buildedQuery = QueryHelper::buildedQuery($query, $bindings);

// select * from authors where id in (1, 2) and name like '%Shakespeare' and updated_at between '2020-07-07 00:37:55' and '2020-07-09 00:37:55'
echo $buildedQuery;

Formatter

その他機能として、クエリのフォーマット機能も提供しています。
こちらは、doctrine/sql-formatter: A lightweight php class for formatting sql statements. Handles automatic indentation and syntax highlighting. を利用しています。
デフォルトはNullHighlighterを利用していますが、Cli、HTMLでのフォーマットも可能です。

詳しくは https://github.com/shimabox/laqu#queryformatter を参照してください。

その他

Facade

Laravelっぽく?ファサードを使ってみました。
実際に作ってみると、どういう仕組で動いているのかなんとなく理解が深まった気がします。

GitHub Actions

みようみまねでGitHub Actionsを使ってみました。
laqu/run-tests.yml at master · shimabox/laqu
これで、PHPのバージョンやLaravelのバージョンごとにテストの実行やlintができているのでそこそこ自信をもって提供できています。

おわりに

というわけで長くなってしまいましたが、Laravelで実行されたDBクエリの確認ができるやつを書いた話は以上となります。
shimabox/laqu: Laqu is Laravel Db Query Helper.
もしよかったら使ってみてください。
(使ってみて、ここはこうしたらいいとか、こうすべきだとかあればプルリクください:muscle:)


  1. QueryBuilderなどを使ってクエリを作るところと実行するところを分けている設計ならば、toSql()を使って確認することも可能でしょうがそんなフェーズはとうに過ぎていた。。 

  2. Basic Database Usage - Laravel - The PHP Framework For Web Artisans のように、enableQueryLog()などを書くのも方法としてはありますが、それをいちいち書くのも面倒だったのでライブラリで吸収しちゃおうという作戦です 

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

Laravel で作ったシステムに Excel をアップロードしたら拡張子が zip になった

環境

環境は ローカルのHomestead本番のVPS の2つで、どちらでも発生。

  • Laravel Framework 5.8.24
  • PHP 7.3.4 (PHP 7.2.16)
  • Ubuntu 18.04.2 LTS (CentOS Linux release 7.6.1810)
  • Homestead (ConoHa VPS Laravelイメージ)

何がどうなった?

エクセルをアップロードし、 $file->guessExtension() で拡張子を取得したところ zip となってしまった。
さらに $file->getMimeType() をみたところ application/zip となっていた。

クライアントの情報を取得する関数を使うと、それぞれ $file->guessClientExtension()
xlsx となり、 $file->getClientMimeType()application/vnd.openxmlformats-officedocument.spreadsheetml.sheet となった。
表にまとめると以下。

extension mime_type
larval zip application/zip
client xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

エクセルの mime_type なっが。

どうして?

finfo がうまく判定できないらしい。
ライブラリをガンガン追って行ったところ、Laravel の問題というよりは、内部で使用している Symfony のクラスの内部で使用している finfo に行きついた。
実際に finfo で mime_type を判定させてみると、 application/zip となってしまった。
application/zip から拡張子を推測すると zip になるという流れである。
StackOverflow の関連記事
上記記事を読むと、ほかの Microsoft の Office 系ファイルもダメらしい。

どうする?

解決策はいくつかあると思う。

  • Symfony の該当クラスは複数の guesser を扱えるので、Laravel で頑張って拡張する
  • finfo を修正する
  • 素直にクライアントの拡張子を利用する

などなど。

自分は 上記のどれも採用せず、拡張子を推測するための関数をつくった(理由は後述)。
作ったと言っても、Laravel/Symfonyが用意したものと、先人の知恵を組み合わせただけだが。

基本方針

  • 拡張子に関してはクライアント(ファイルアップロード者)を信じるという選択は取りたくない
  • Laravel を拡張するのはしんどそう
  • finfo を修正するのは移植性がない
  • 不具合報告があったのはとりあえず excel だけ

参考ソース

public function guessExtension(UploadedFile $file): string
{
    $extension = $file->guessExtension();
    if ($extension !== 'zip') {
        return $extension;
    }

    $guessedMimeType = $file->getMimeType();
    $clientMimeType = $file->getClientMimeType();
    if ($guessedMimeType === $clientMimeType) {
        return $extension;
    }

    $map = [
        'application/vnd.ms-word.document.macroEnabled.12' => 'docm',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',

        'application/vnd.ms-powerpoint.template.macroEnabled.12' => 'potm',
        'application/vnd.ms-powerpoint.addin.macroEnabled.12' => 'ppam',
        'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => 'ppsm',
        'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => 'pptm',
        'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx',
        'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx',
        'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',

        'application/vnd.ms-excel.template.macroEnabled.12' => 'xltm',
        'application/vnd.ms-excel.addin.macroEnabled.12' => 'xlam',
        'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => 'xlsb',
        'application/vnd.ms-excel.sheet.macroEnabled.12' => 'xlsm',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
    ];

    return $map[$clientMimeType] ?? $extension;
}

作った後に「 Symfony の同様の力技を行使する guesser を使う手もあったな」などと思ったけど、いろんな mime_type を検討するのはしんどいので、とりあえずこれで。

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

Dockerで構築したLaravel環境に、VSCodeでステップ実行デバッグを仕掛ける

元記事 Dockerで構築したLaravel環境に、PHPStormでステップ実行デバッグを仕掛ける
← 元元記事 最強のLaravel開発環境をDockerを使って構築する【新編集版】

目的

Dockerで構築したLaravel環境で実行されているPHPコードに対して、
Microsoft製の「VSCode」からステップ実行デバッグができるようにしたい。

概要

PHP定番デバッグツール xdebug を使用して実現していきます。
元記事でサーバサイド側の設定まで設定済みであることが前提。
本記事ではクライアント側であるVSCodeの設定のみ記載します。
(元記事、元元記事共に大変参考になりました。感謝。。)

環境

  • 冒頭に書いた元記事および元元記事の手順でLaravel環境を構築済であること。
  • VSCode最新版を使って開発している方。(Mac版で動作確認済。)

方法

クライアント側作業(お手元のMac)

VSCodeを起動する。

PHP Debugをインストールしていない場合は PHP Debug - Visual Studio Marketplace をインストールしておく

VSCodeでdocker-laravel プロジェクトを開く。

image.png

メインメニューからデバッガ設定画面を開く。

(設定ファイルがない場合は初回設定の際に作成されます。)
image.png

launch.jsonを作成後、以下の設定を貼り付ける

  • portはサーバ側で設定した値と同じになるようにする
  • pathMappings
    • サーバ側のアプリケーション配置場所:"/work/backend"
    • クライアント側のアプリケーション配置場所:"${workspaceRoot}/backend"
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "port": 9000,
            "pathMappings": {
                "/work/backend": "${workspaceRoot}/backend"
            }
        }
    ]
}

.vscode/launch.jsonが下記画像のように作成されていればOK
* ドキュメントルートがDocker-backendの下に

image.png

ブレークポイントの設定

  • 一時停止した時の挙動が確認ができるように、変数に文字をセットするようコードを修正。
-- Web.php
<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    $hoge = 'welcome';
    return view($hoge);
});
  • Web.phpにブレークポイントをセット
    image.png

デバッグ開始

image.png

ブラウザでPHPサーバーにアクセスしてみる。

http://localhost/

18行目で処理が一時停止、左側のタブで変数に格納された文字列を確認できる

変数にマウスオーバーしても値を確認できる

image.png

参考リンク

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