メモ帳

読むな

Laravel でアクセストークンを header に含むときのAPIのテストを書く

f:id:IlIIlIIIlIIlI:20220129185719p:plain

いつ使うの?

  • SPAではないプロジェクト(アクセストークンの取得タイミングがMPAでのログイン時)
  • 一部画面でアクセストークンを含むAPIを叩いている

コード

<?php

use Tests\TestCase;

class SampleControllerTest extends TestCase
{
    public function setUp(): void
    {
        parent::setUp();

        // ログイン処理
        $res = $this->post('/login', [
            'email' => 'hoge@example.jp',
            'password' => 'password'
        ]);
        // 取得したアクセストークンをプロパティに格納
        $this->accessToken = $res->baseResponse->getSession()->get('access_token');
    }

    public function testAPIリクエスト()
    {
        // プロパティに入っているアクセストークンを使用してリクエスト
        $res = $this->post('api/hoge', ['product_id' => 1], ['Authorization' => 'Bearer ' . $this->accessToken]);

        $res->assertOk()->assertJson(['succeeded' => true]);
    }
}

要点

  • setUp() 時にアクセストークンの発行処理を実施し、プロパティにアクセストークンを入れておく
  • テストの時にプロパティに入っているアクセストークンを含めてリクエストの発行をする
  • アクセストークン取得時のリクエストはAPIのエンドポイントではないのでアクセストークン自体は \Illuminate\Http\Response が入っている baseResponse から取得する
    • API経由で取得する場合は $res->json($key) で取得できるっぽい(未検証)
      (※decodeResponseJson()の引数にkeyを指定して取得できてたみたいだけどLaravel8からは引数を取らないみたい)

github.com

readouble.com

qiita.com


Laravel で画像をアップロードする Controller のテストを書く

Laravel で画像アップロードのController周りのテストを書いたことがなかったので備忘録

<?php

use Tests\TestCase;
use Illuminate\Http\UploadedFile;

class SampleControllerTest extends TestCase
{
    public function test画像アップロードができる()
    {
        $res = $this->post('image/upload', ['images' => UploadedFile::fake()->image('hoge.jpg')]);

        $res->assertRedirect('image/upload/completed');
        $res->assertSessionHas('success', 'アップロードに成功しました。');
    }
}

UploadedFile facadeのfake()を使えばOK
勝手に面倒くさそうと思ってたけどめちゃめちゃ簡単だった👌

readouble.com

親のチェックボックスに紐付けたチェックボックスを全てチェックする

親のチェックボックスのチェック状態を親に紐付けた子供のチェックボックスにも反映させたい
↓こういうやつ

これをピュアJSで書いたときの備忘録

コード

解説

HTML

  1. チェックボックスに子供を紐付ける用のカスタムデータ属性(data-id="1")をつける
    (親であることを判別できるのであればclass名などで管理してもOK)
<label>親チェック1<input type="checkbox" data-id="1" class="check-parent"></label>
  1. 親に紐付ける子供となるチェックボックスに親のid(data-parent-id="1")を紐付ける
<label>親1子供1<input type="checkbox" data-parent-id="1"></label>

JavaScript

  1. 親となるチェックボックス要素のchangeイベントをループ処理で実施する
  2. 親のdataset.idに紐付いている子供の要素を取得して, ループ処理で親のチェック状態を子供のチェック状態と同じにする
Array.from(document.querySelectorAll('.check-parent')).map(checkParent => {
  checkParent.addEventListener('change', () => {
    Array.from(document.querySelectorAll(`[data-parent-id="${checkParent.dataset.id}"]`)).map(checkChild => {
      checkChild.checked = checkParent.checked
    })
  })
})

Carbon の diffInDays() は時間まで見ている

f:id:IlIIlIIIlIIlI:20220129190244p:plain

みんな大好きCarbonで対象日が今日の何日後かを知りたいときに, 想定していた挙動と違っていたのでメモ

対象日が本日の何日後かを知りたい

2021-11-15が本日(2021-11-14)の何日後かを知りたいとき

>>> now()->diffInDays(Carbon\Carbon::parse('2021-11-15'))
=> 0

期待値は1だったけど, 0が返ってきた

実装を見てみる

Carbon\Traits\Difference を見てみると, 継承元の DateTime クラスの diff() を見に行ってたので DateTime での挙動も確認してみる

>>> (int) (new DateTime())->diff(new DateTime('2021-11-15'))->format('%r%a')
=> 0

(※ intへのcastとformat()はCarbon準拠)

(当たり前なんだけど)同じく0が返ってくる

現在時刻の時間を指定してみる

>>> now()->setTime(0, 0)->diffInDays(Carbon\Carbon::parse('2021-11-15'))
=> 1
>>> (int) (new DateTime())->setTime(0, 0)->diff(new DateTime('2021-11-15'))->format('%r%a')
=> 1

期待値の1が返ってきた。

対応策

現在時刻と対象日時の日付だけを考慮してdiffを取りたいときは現在日時刻の時間もセットしよう!

感想

DateTime クラス関してはdiffっていう命名からしてまっとうな挙動だと思うんだけど, Carbon のdiffInDays命名ややこしすぎ
日時を指定されても日数のdiff返せや(横暴)
ちなみに自分は Laravel 触るまでCarbonの存在知らなかったマンです。

※実際のコードにはImmutableクラス使おうね


github.com


PHPで半角数字を漢数字の単位付きの表記に変換したい

f:id:IlIIlIIIlIIlI:20211107203557p:plain

53000053万みたいに漢数字の単位を付けたかった。
タイトルに漢数字って書いてあるから詐欺ってるみたいだけど五三万への変換は今回はやりません。🙇🏿‍

早速コード

<?php

function convert($value) {
    // 1000以下の場合は単位を付けずに返却
    if ((int) $value < 1000) {
        return (string) $value;
    }

    $units = ['', '万', '億', '兆', '京'];

    // 右から桁数を分割していく
    $splits = array_reverse(str_split(strrev($value), 4));

    // 桁数によって使用する単位の調整
    array_splice($units, count($splits));
    $filterUnits = array_reverse($units);

    $convert = '';
    // 反転した数値を元に戻して単位をつける
    foreach ($splits as $index => $split) {
        // 0000の場合、表記を省略する
        $convert .= (int) $split === 0 ? '' : number_format(strrev($split)) . $filterUnits[$index];
    }

    return $convert;
}

assert('私の戦闘力は53万です。' === '私の戦闘力は'.convert('530000').'です。'); // true

感想

  • 単位が飛ぶ時の表記をどうしようか迷ったけど, 単位が0000の場合は省略するようにした。
    (100000001の場合, 1億0000万0001ではなく1億1の表記になる)
  • 反対に, 漢字(53万) → 半角数字(530000)に変換するのも考えたけど, 意外とやることが多くて諦めた。
    必要に迫られたらやる。(やらないフラグ)
  • 漢字単位への変換前に文字列に変換すれば京以上の単位も付けれるんだろうけど, お金周りの変換がしたかっただけだから考慮してない。PHP_INT_MAX を超過する数値を扱いたい人はこっち来ないで😭😭😭
  • 全体的にトリッキーなので誰か別の方法ご存知でしたらご指摘ください🙏🏻
  • JSでやったほうが需要ありそう

github.com

packagist.org



ブラウザの Console をタイマー代わりに使う

電卓使いたいときにブラウザの Console 使う人間なので、タイマーも Console で完結させたかった

1秒ごとに Console に秒数を表示する

let minutes = 0;
const intervalId = setInterval(countMinutes = () => {
  ++minutes;
  console.log(minutes);
}, 1000);

特定の秒数になったらカウントを停止する

let minutes = 0;
const intervalId = setInterval('countMinutes()', 1000);
const countMinutes = () => {
  if (minutes === 5) {
    clearInterval(intervalId);
    alert(`${minutes}秒が経過しました。`);
    return;
  }
  ++minutes;
  console.log(minutes);
}

特定の秒数になったら、ダイアログを押すまで通知音を鳴らす

let minutes = 0;
const intervalId = setInterval('countMinutes()', 1000);

const audio = new AudioContext();
const oscillator = audio.createOscillator();
oscillator.type = 'sign'
oscillator.connect(audio.destination);

const soundStart = () => {
  oscillator.start();
}
const soundStop = () => {
  oscillator.stop();
}

const countMinutes = () => {
  if (minutes === 5) {
    clearInterval(intervalId);
    soundStart();
    alert('タイマーを止める。');
    soundStop();
    return;
  }
  ++minutes;
  console.log(minutes);
}

いつ使うの?

PCがネットに繋がらない状況でカップ麺を作らなければならないとき

感想

こんなん書くくらいだったら普通にタイマーアプリ使うわ
最低限通知音は必須だけど毎回書いとれん😊

もっと簡潔に書ける方法あったら誰か教えて下さい 🙏🏻

Bootstrap を使ってテーブルの行をクリックした時にコンテンツを展開したい

テーブルの各行をクリックした時に行の下にコンテンツを表示する。
一覧に表示する列数が多いけど横スクロールしたくないときに使う。

tr > td の中に div を書いてたりするのでマークアップ警察は見るな

要点

① クリックする行の tr タグに開閉トリガーをつける

<tr data-toggle="collapse" data-target="#collapse-1">

② クリックする行の tr タグの直下に tr > td でラップした開閉コンテンツを設置する
td 要素は1つだけにして, 開閉トリガーの列数分の colspan で結合する

<tr>
    <td colspan="4" class="p-0">
        <div class="collapse" id="collapse-1">
            <p>開閉コンテンツ</p>
            <p>
                親譲りの無鉄砲で小供の時から損ばかりしている。小学校に居る時分学校の二階から飛び降りて一週間ほど腰を抜かした事がある。なぜそんな無闇をしたと聞く人があるかも知れぬ。別段深い理由でもない。新築の二階から首を出していたら、同級生の一人が冗談に、いくら威張っても、そこから飛び降りる事は出来まい。弱虫やーい。と囃したからである。小使に負ぶさって帰って来た時、おやじが大きな眼をして二階ぐらいから飛び降りて腰を抜かす奴があるかと云ったから、この次は抜かさずに飛んで見せますと答えた。(青空文庫より)
            </p>
        </div>
    </td>
</tr>

感想

個人的にはあまり好きなUIではないので必要に迫られた時以外使いたくない🤔

Laravel で ORDER BY を複数指定する

Laravel で SELECT * FROM samples ORDER BY column1 DESC, column2 ASC みたいに ORDER BY を複数指定したい場合の書き方

<?php
App\Models\Sample::orderBy('column1', 'desc')->orderBy('column1', 'asc')->get();

もしくは

<?php
App\Models\Sample::orderByDesc('column1')->orderBy('column1')->get();

何故か毎回連想配列でできると思いこんでApp\Models\Sample::orderBy(['column1' => 'desc', 'column1' => 'asc'])->get();みたいに書いてはエラー→ググる→修正のループを繰り返してたけどこれで覚えるだろ。。。

参考

readouble.com

Vue.js でファイルアップロードをする

Vue.js というより axios でファイルアップロードをしたかった
保存ボタンがなく、アップロード時にサーバーに送るのを想定
サーバーサイドのFWは Laravel を想定

<template>
  <div>
    <!-- ファイルアップロード用の input -->
    <input
      type="file"
      ref="image"
      accept="image/*"
      name="image"
      @change="uploadImage"
    />
  </div>
</template>

<script>
import axios from "axios";

export default {
  name: "InputFile",
  methods: {
    uploadImage() {
      // parameter の用意
      const params = new FormData();
      params.append("image", this.$refs.image.files[0]);

      axios
        .post(`${process.env.MIX_APP_URL}ファイルアップロード先のURI`, params, {
          headers: {
            "Content-Type": "multipart/form-data",
            Authorization: "XXXXXXXXXXXX",
          },
        })
        .then((res) => {
          console.log(res, res.data);
          if (res.status !== 200) {
            console.error("画像のアップロードに失敗。");
            return;
          }
          // サーバー側のレスポンスに合わせてエラーハンドリングなど
          if (!res.data.succeeded) {
            console.error("画像のアップロードに失敗。");
            return;
          }
          // アップロード成功時の処理
          console.log("画像アップロードに成功。");
        })
        .catch((error) => {
          console.error(error, error.response);
          // Laravel のバリデーションエラーの場合のレスポンスに合わせてエラーメッセージを出力
          if (
            error.response.status === 422 &&
            error.response.data.errors.length !== 0
          ) {
            console.error(error.response.data.errors.image);
            return;
          }
          // その他のエラー
          console.error("画像のアップロードに失敗。");
          return;
        });
    },
  },
};
</script>

axios でファイルアップロードってやったことないからどうやるんだろうって思ってたけどよくよく考えたら FormData でパラメータをセットしてPOSTすればいいだけの話だった

参考

qiita.com

HTML と CSS のみでドラえもんを作る

(割とやりつくされたネタだから今更感すごいけど。。。)
[ドラえもん CSS] とかでググると実装方法がわかってしまうため今回は終始ググらないようにして実装

できたもの

f:id:IlIIlIIIlIIlI:20211010193053p:plain:w200

実装

感想

所詮丸と直線の組み合わせだし position とか使わんくても Flexbox だけでモダンなドラえもん作ってやるぜ!!!って思って手を出したけど意外とムズかった。position 使いまくって結局泥臭いコードになってる。(HTML構造のせいかもだけど)
完成後に他の方のドラえもんも見て回ったけど↓このドラえもんの完成度が圧倒的だった。体もあるし

shopdd.jp

後これも。
俺の想定してたドラえもんの口がまさにこれ qiita.com

おまけ

作成過程で生み出されたバケモンたちの供養

f:id:IlIIlIIIlIIlI:20211010165821p:plain:w200f:id:IlIIlIIIlIIlI:20211010165816p:plain:w200
f:id:IlIIlIIIlIIlI:20211010165814p:plain:w200f:id:IlIIlIIIlIIlI:20211010165812p:plain:w200



JSでブラウザの位置情報を取得する

(function () {
    if (!navigator.geolocation) {
        console.warn("Geolocation APIを利用できない環境");
        return;
    }
    navigator.geolocation.getCurrentPosition(
        (positions) => {
            // 位置情報の取得成功時の処理
            console.log(positions.coords.latitude, positions.coords.longitude);
        },
        (error) => {
            // 位置情報の取得失敗時の処理
            const errors = {
                1: '位置情報の利用が許可されていない。',
                2: '位置情報の取得に失敗。',
                3: 'タイムアウト。'
            };
            console.error(error.code, errors[error.code])
        },
        {
            enableHighAccuracy: true,
            timeout: 5000,
            maximumAge: 0,
        }
    );
}());

options詳細

option デフォルト 説明
enableHighAccuracy false 精度の高い位置情報を取得するか GPSを使うことができる場合に使う
timeout 無制限 タイムアウトまでの時間
maximumAge 0 取得済みの位置情報の有効期限

ブログ用に書いてるから可読性死んでるけどプロダクトに使うときはコールバック関数は別で定義しようね。。。

参考

developer.mozilla.org developer.mozilla.org developer.mozilla.org


[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

HTML5 Geolocation Bringing Location to W...
価格:1179円 (2021/10/9時点)


JSで任意の個数分の配列を作成する

PHPで言うところの range(1, 20) をやりたかったけどJSにはそれらしき機能がなく毎回調べているのでメモ

[...Array(5).keys()] // [0, 1, 2, 3, 4]
Array.from(Array(5).fill().keys()) // [0, 1, 2, 3, 4]

indexを0以外から始めたいときはmapで足してあげる

[...Array(5).keys()].map(row => row+3) // [3, 4, 5, 6, 7]

単純に個数分だけ作成したいときはArray(5).fill()でOK

配列を任意の個数で分割したい

array_chunk() みたいに全部均等ではなく, 3個, 2個, 2個, 6個, 1個, 1個 ... みたいに分割数が可変な配列を作りたかったときに地味にハマったのでメモ

<?php
$src = range(1, 15);
$chunks = [3, 2, 2, 6, 1, 1]; // 分割したい個数
$offset = 0;
$values = [];
foreach ($chunks as $chunk) {
    $values[] = array_slice($src, $offset, $chunk);
    $offset += $chunk;
}

結果

array(6) {
  [0]=>
  array(3) {
    [0]=>
    int(1)
    [1]=>
    int(2)
    [2]=>
    int(3)
  }
  [1]=>
  array(2) {
    [0]=>
    int(4)
    [1]=>
    int(5)
  }
  [2]=>
  array(2) {
    [0]=>
    int(6)
    [1]=>
    int(7)
  }
  [3]=>
  array(6) {
    [0]=>
    int(8)
    [1]=>
    int(9)
    [2]=>
    int(10)
    [3]=>
    int(11)
    [4]=>
    int(12)
    [5]=>
    int(13)
  }
  [4]=>
  array(1) {
    [0]=>
    int(14)
  }
  [5]=>
  array(1) {
    [0]=>
    int(15)
  }
}

ちなみにハマったとこは最初 $offset 使わずに $values[] = array_slice($src, count($values), $chunk); みたいな書き方してて、当たり前だけど offset が1個ずつしか増えてかんくて, 「なんで!??!?!?!?!」ってなってた🥰クソバカ🥰

参考

www.php.net

bashalog.c-brains.jp

Terminal 起動時に 「You have new mail.」 と表示された場合の対処法

f:id:IlIIlIIIlIIlI:20210926182834p:plain

ホストマシンに設定したテスト用の cron が失敗してた時にメールが届いてた
放置してたけど地味に鬱陶しかったので対処したときのメモ


$ mail
>U  1 # 未読のメールが件数分表示される
>N  2
>N  3 
>N  4 
? d*
? q
  1. mail コマンドで未読メールの一覧を表示
  2. 適当に件名で判断して全削除OKの場合はそのままd*
  3. qで終了

削除オプション

command 意味
d1 1だけ削除
d1 2 1と2を削除
d1-10 1~10を削除
d* 全削除

先頭のアルファベットの意味

alphabet 意味
N 新着
U 未読

参考

linux.just4fun.biz

pc-karuma.net

navymobile.co.jp

Laravel でQueryBuilderで組み立てられたSQLを確認する方法

QueryBuilder(or Eloquent ORM)で組み立てたSQLを確認したい時に使う
いずれも tinker で実施

①QueryBulderから確認する場合

>>> App\Models\User::where('id', 1)->toSql()
=> "select * from `users` where `id` = ?"

②複数のQueryBuilderから確認したい場合

>>> DB::enableQueryLog()  # ログの出力をONにする
=> null
>>> App\Models\User::find(1) # SQLを確認したい処理分だけ実行する
=> App\Models\User {#370
     id: 1,
     name: "example",
     email: "example@example.com",
   }
>>> DB::getQueryLog() # 実施した分のSQLが出力される
=> [
     [
       "query" => "select * from `users` where `users`.`id` = ? limit 1",
       "bindings" => [
         1,
       ],
       "time" => 2.76,
     ],
   ]
>>> DB::flushQueryLog() # ログの中身を空にする

適当なタイミングで DB::flushQueryLog() をすればログの中身が空になる
毎回空にするのであれば dump(DB::getQueryLog()) && DB::flushQueryLog() を実行すれば出力と同時にログの中身も空にしてくれる

参考

qiita.com