たけるのプログラミング

作ったものとか、気ままにアップします。

内定者ポータルサイトのメール通知機能が機能してなかったから、LINEに通知できるシステムを作った。

タイトル通り内定先のポータルサイトのメール通知機能が機能してなかった。

なので人事の方からのメッセージが来ているかいちいちログインして確認しなくては行けなかった。

結構めんどくさい。なのでサイトが更新(新着メッセージが来たら)されたらLINEに通知される仕組みを作った。

結構簡単で、

  • curlを使ってhtmlソースを取得する。
  • 取得したhtmlソースから正規表現を使って、個々のメッセージを囲んでいるdiv要素を全て抜き出し配列に格納。配列の要素数がメッセージ総数となる。
  • (実行1回目)メッセージ総数を適当なテキストファイルに記録。DB使ってもいいけど、大袈裟かも。
  • (実行2回目以降)メッセージ総数とテキストファイルに記録されている数を比べて異なる場合(この場合、メッセージが削除された時も通知しちゃうけど)、メッセージ総数をテキストファイルに上書きして、webhookを使ってLINEに通知する。

またこのシステムを自動で動かすためにcronを使う。

0 */1 * * * php /Applications/MAMP/htdocs/実行したいファイル.php

みたいにすることで毎時 0分に1時間おきにプログラムを実行してくれる。
crontab の書き方|プログラムメモ

LINEに通知が来るたび、しっかり機能してる〜!と思い嬉しい気持ちになってる。

では!

【laravel】modelクラスのドキュメントにwhereメソッドないけど何で?

よくこんなコードを参考書やネットで見かける。

 $items = Tweet::where('name','=','レブロンジェームス')->get();

ここでTweetモデルはIlluminate/Database/Eloquent/Modelを継承してるから、Modelのドキュメントにwhereメソッドについて載ってると考えた。

しかし
Illuminate\Database\Eloquent\Model | Laravel API

をみてみるとwhereメソッドがない。

どういうこと?

以下の記事が解決してくれた。

【Laravel5】Eloquent ORMと2つのBuilderクラス|Laravel|PHP|開発ブログ|株式会社Nextat(ネクスタット)

Illuminate\Database\Eloquent\Model | Laravel API
Illuminate\Database\Eloquent\Builder | Laravel API
Illuminate\Database\Query\Builder | Laravel API

【SQL】内部結合と外部結合について簡単な説明

※たぶん徐々に追記していきます。


今回のサンプルテーブル

membersテーブル

id name team_id
1 レブロンジェームス 1
2 クリスポール 2
3 デビンブッカー 2
4 カールアンソニータウンズ 3
5 デマーデローザン 4

teamsテーブル

id name
1 レイカーズ
2 サンズ
3 ウルブス

内部結合

結合条件のカラムのフィールドの値が一致するレコードのみ取得する。
今回の例だと、membersテーブルのteam_idの値とteamsのidの値が一致したものを結合し、レコードを取得している。逆に一致していないとレコードを取得しない。デローザンのteam_idカラムのフィールド値の4はteamsテーブルのidカラムのフィールド値に存在しないため結合もできないし、レコードも取得されない。


SELECT * FROM members m JOIN teams t ON t.id = m.team_id

id name team_id id name
1 レブロンジェームス 1 1 レイカーズ
2 クリスポール 2 2 サンズ
3 デビンブッカー 2 2 サンズ
4 カールアンソニータウンズ 3 3 ウルブス






外部結合

基準のテーブルを決めて、そのテーブルの全レコードを取得し、内部結合同様に結合条件を利用して結合する。

LEFT JOIN

leftなのでmembers m LEFT JOIN teams tにおけるmembersテーブルを基準とする。

SELECT * FROM members m LEFT JOIN teams t ON t.id = m.team_id

id name team_id id name
1 レブロンジェームス 1 1 レイカーズ
2 クリスポール 2 2 サンズ
3 デビンブッカー 2 2 サンズ
4 カールアンソニータウンズ 3 3 ウルブス
5 デマーデローザン 4 NULL NULL

基準であるmembersテーブルの全レコードを取得したが、m.team_id = 4 = t.id となるレコードがなかったため t.id と t.nameがnullとなっている。
内部結合の場合はそもそもm.id m.nameも取得されない。

RIGHT JOIN

left joinの考え方と同じ

SELECT * FROM members m RIGHT JOIN teams t ON t.id = m.team_id

id name team_id id name
1 レブロンジェームス 1 1 レイカーズ
2 クリスポール 2 2 サンズ
3 デビンブッカー 2 2 サンズ
4 カールアンソニータウンズ 3 3 ウルブス

【PHP】正規表現を学ぶ【その1】

Linuxを勉強してたら正規表現の知識が乏しかったので勉強する。

正規表現をざっとまとめた。
引用参照url
正規表現 | 任意の一文字にマッチする:ドット(.)
正規表現 | いずれか一文字にマッチする:角括弧([...])
正規表現 | バックスラッシュ(\)+文字を使った文字クラスの略記法
正規表現 | 文字列の先頭と文字列の末尾の位置にマッチする:キャレット(^) ドル記号($)
正規表現 | 単語の先頭と単語の末尾にマッチする:\b \B
正規表現 | 直前の文字の繰り返し:* + ? {num} {min,max}


基本的なやつ

* 任意の一文字にマッチする
[] 中のいずれかにマッチする。[0-9]0から9 [a-z]aからz [A-Z]AからZ また[^]とすることで中で指定した文字以外とマッチする
\d 数字
\D 数字以外
\w 英数字とアンダーバー
\W 英数字とアンダーバー以外
\s 空白文字
\S 空白文字以外
^ 文字列の先頭にマッチする
$ 文字列の末尾にマッチする
\b 単語の先頭または単語の末尾にマッチする
\B 単語の先頭及び単語の末尾以外にマッチする
量指定
{num} 直前の文字をnum回繰り返す
{min,} 直前の文字をmin回以上繰り返す
{,max} 直前の文字をmax回以下繰り返す
{min,max} 直前の文字をmin以上、max以下繰り返す
* 直前の文字が0回以上連続する
+ 直前の文字が一回以上連続する
? 直前の文字が0か1回現れる

他にも
()によるグループ化や| (縦線)を使ったパターンといったテクニックもある。

このqiitaの表が上に載ってないやつも載っててわかりやすい。
正規表現まとめ - Qiita

実際にPHP正規表現を使ってみる。

今回PHPの関数であるpreg_matchを使う。引数として渡す文字列に対して正規表現による検索を行う。
PHP: preg_match - Manual

実際に使ってみる。
ユーザーが携帯電話番号を入力したとして、それが本当に携帯電話番号かpreg_matchを使って検証する。

<?php
//携帯電話番号
$test_data = '080-0000-0000';
echo preg_match('/^(070|080|090)-\d{4}-\d{4}$/',$test_data,$array);
var_dump($array);

実行結果

1
array(2) {
  [0]=>
  string(13) "080-0000-0000"
  [1]=>
  string(3) "080"
}

文字列の中に正規表現のパターンがあったので返り値として1が返されています。もしなかった場合は0が帰ってきます。また第三引数に配列を渡してあげることで、その配列に検索結果を代入することができます。
[0]にパターン全体にマッチしたテキストが代入されます。また[1]にはサブパターンにマッチした文字列が代入されるとありました。このサブパターンとは
PHP: サブパターン - Manual
によると

丸カッコで括られたパターンのこと

とあった。つまり上の例の(070|080|090)。このサブパターンに080がマッチしているので、この080が[1]に代入されている。このように2番目のサブパーンにマッチした文字は[2]に代入される。

【PHP】N+1問題をLaravel Debugbarを使って検証してみる【Laravel】

N+1問題とは
【Ruby on Rails】N+1問題ってなんだ? - Qiita

ループ処理の中で都度SQLを発行してしまい、大量のSQLが発行されてパフォーマンスが低下してしまう問題のこと。

1回のクエリ発行でN件のレコードを取得し、

それぞれN件のレコードが持っているリレーション先のテーブルのレコード

を取得しようとする(N回のクエリ発行)とこのN+1問題が起こる。

具体例

実際に以下のようなリレーションを持つテーブルがあったとする。

f:id:takeru232423:20220314133636p:plain

membersテーブルにて外部キーをclub_idとしてclubsテーブルとリレーションを作る。

テーブルに対応したモデルを作る。

Member.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Member extends Model
{
    use HasFactory;
    public function Club_content()
    {
        return $this->hasOne('App\Models\Club','id','club_id');
    }
}

Club.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Club extends Model
{
    use HasFactory;
}

ルーティング

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\testnplus1;

Route::get('/',[testnplus1::class,'index'])->name('test');


そして以下のようなコントローラーとブレードを書いてN+1問題を起こす。
testnplus1.php

<?php

namespace App\Http\Controllers;

use App\Models\Member;


class testnplus1 extends Controller
{
    //
    public function index()
    {
        $members = Member::all();
        return view('showname',['members' => $members]);
    }
}

blade

@foreach ($members as $member)

<div>
    <p>{{$member->name}}</p>
    <p>{{$member->Club_content->name}}</p>
</div>

@endforeach

そしてlaravel Debugbarを利用してどんなSQLクエリが実行されたか確認してみる。すると以下のような結果となった。f:id:takeru232423:20220314141526p:plain

まずコントローラ内のMember::all()はテーブル内のレコードを全て取得するメソッドなので、上記SQLの"select * from members"に対応している。
問題なのはそれ以下のSQLクエリである。ブレードの繰り返し処理内の"$member->Club_content->name"を実行するたびにクエリを実行することとなるのでデータベースに負担をかけてしまうこととなる。今回は5、6個のレコードだけでテストしているが、このレコード数が大きくなればなるほどデータベースへの負荷を高めてしまうこととなる。

このN+1問題を解決するためにイーガーロードを利用する。イーガーロードを利用することでN回行ったSQLクエリを1回のSQLクエリにまとめることができる。

イーガーロードを利用するにはwithメソッドを使用する。
Eloquent:リレーション 8.x Laravel

コントラーのコードを以下のように変更する。

<?php

namespace App\Http\Controllers;

use App\Models\Member;


class testnplus1 extends Controller
{
    //
    public function index()
    {
        //N+1問題が発生するコード
        //$members = Member::all();

        //イーガーローディングによりN+1問題を防ぐコード
        $members = Member::with('Club_content')->get();
        return view('showname',['members' => $members]);
    }
}

Laravel Debugbarを使ってクエリの発行数を見てみると7→2になっている。
f:id:takeru232423:20220314143412p:plain

これでデータベースへの負荷を下げることができる。

機能実現だけでなく、パフォーマンスのことも考えることが大切だな〜。

【PHP】issetとemptyについてのメモメモ♪

いつも感覚的に使っちゃてたissetとemptyについてドキュメントやQiitaを見ながら再確認。


isset

https://www.php.net/manual/ja/function.isset.php
変数が宣言されており、かつ その値がnullではない
→true
そうでない場合
→false

empty

※ !isset($x) || $x==false と同じ意味
https://www.php.net/manual/ja/function.empty.php
変数が存在しない場合  値がfalseに等しい場合
→true
そうでない場合
→false

値がfalseに等しいとはどういう意味か?PHPは動的型付け言語なので名前通り動的に型付けがされる。

<?php
var_dump((bool) "");        // bool(false)
var_dump((bool) "0");       // bool(false)
var_dump((bool) 1);         // bool(true)
var_dump((bool) -2);        // bool(true)
var_dump((bool) "foo");     // bool(true)
var_dump((bool) 2.3e5);     // bool(true)
var_dump((bool) array(12)); // bool(true)
var_dump((bool) array());   // bool(false)
var_dump((bool) "false");   // bool(true)
?>

PHP: 論理型 (boolean) - Manual より引用

Qiitaにあった使えそうな早見表
PHP isset, empty, is_null の違い早見表 - Qiita

【PHP】オートロードとクラスインポート

PHPのオートロード(autoload) - Qiita
【PHP】Composerを使用してクラスのオートロードを行う | Points & Lines
【PHP超入門】名前空間(namespace・use)について - Qiita



laravel使ってると何でuseだけでクラスが使えるようになるの?

結論autoload(オートロード)とクラスインポートの組み合わせ。

フルスクラッチで挙動について確認してみる。

今回のディレクトリの構成。

f:id:takeru232423:20220309132929p:plain

オートロードを使うためにcomposerを利用する。

composer.jsonにて

{
 "autoload": {
        "psr-4": {
            "app\\": "./"
        }
    }
}

と記述しcomposer dumpautoloadを実行する。上記コードは名前空間とするapp と 現在のディレクトリ(.)を紐付けている。
ディレクトリと同様の構造の名前空間を指定することでオートロード対象となる。
そのためクラスファイル(ファイル名とクラス名が同じファイル)に名前空間に上記で指定した「app」+ 「クラスファイルがあるまでのディレクトリ構造」
を指定する。なので以下のようになる。

test1.php

<?php
namespace app\a;

Class Test1
{
    function printa()
    {
        echo "a!!!";
    }
}

test2.php

<?php
namespace app\b;

class Test2{
    function printb()
    {
        echo "b!!!";
    }
}

そして以下のように利用
try.php

<?php
require_once("vendor/autoload.php");

use app\a\Test1;
use app\b\Test2;

(new Test1)->printa();
(new Test2)->printb();

それぞれのファイルをrequireしていないことがわかる。またuseを使いクラスをインポートし、コードが綺麗になる。

オートロードを勉強することでlaravelのコードの見方が変わるかも。