20201212のlaravelに関する記事は9件です。

Laravel入門 第2版 Chapter5 をMariaDBで学習するためのメモ

はじめに

私はLaravelを学習するにあたり通称「青本」である
「PHPフレームワーク Laravel入門 第2版」  掌田 津耶乃・著
にて学習をしています。

途中でDBを絡めて学習を進める章があり
そこではSQLiteを使用していまが、私はMariaDBにて学習を進めたかったので
その時に躓いたところを備忘録として、随時記録していきます。

SQLでテーブルを作成する

「SQL利用の場合」の項にてSQLにてテーブルを作成する説明があるのですが
ここでのSQL文はSQLiteでの作成方法なので注意が必要です。
MariaDBにてテーブルを作成するには以下の通り。

リスト5-1
CREATE TABLE people (
    id INTEGER PRIMARY KEY AUTO_INCREMENT,
    name TEXT NOT NULL,
    mail TEXT,
    age INTEGER
);


念のため作成したテーブルの確認

カラムの確認
SHOW columns FROM people;



結果
image.png

※以降、随時更新していきます。

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

Laravel 外部キーが消せない

概要

あ、このテーブル、ユーザーIDで管理すると思ったけど違った!このカラム不要やわ!

$table->dropColumn('user_id');

php artisan migrate ポチーーーーー!!

エラーーー!ファーーーーーwwwww

となったときの対処法です。

結論

外部キー制約と、インデックスを消してからでないと、カラムは削除出来ない。

   public function up()
    {
        Schema::table('items', function (Blueprint $table) {
            $table->dropForeign('items_user_id_foreign');
            $table->dropIndex('items_user_id_index');
            $table->dropColumn('user_id'); 
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('items', function (Blueprint $table) {
            $table->integer('user_id')->unsigned()->index();
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); 
        });
    }

DBから直接消そうとしてもエラーになるそうなので、DB的にそういうものだと覚えました。。。

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

Laravel組み込みの同時実行制御

これはOPENLOGI Advent Calendar 2020の13日目の記事です。

はじめに

Laravelには、Redisを利用して、同じ処理を同時にいくつ実行できるかを制御するためのモジュールがあります。
弊社では、現在laravelの7.xを使っていますが、7.xの情報については、基本的には以下を参照いただければ理解できると思います。
https://readouble.com/laravel/7.x/ja/queues.html

基本的にはドキュメントを読めばだいたいのことを理解できるのですが、やや言葉足らずな部分があります。
今回は、それらのモジュールの同時実行制御が、実際にどのように動くのか、検証していきます。
また、この記事ではlaravel 7.xを前提としています。

説明対象

まず、どこから利用するかですが、Illuminate\Support\Facades\Redisというfacadeが用意されており、そこから利用できます。
また、今回解説する関数については、Illuminate\Redis\Connections\Connectionに関数の定義があります。

で、利用する関数としては、主に2つあります。
funnelthrottleです。
一つ一つ解説していきます。

funnel

funnelは、一度に実行できるジョブの数を制限するというものです。
実際の実装は以下のクラスに定義されています。

  • Illuminate\Redis\Limiters\ConcurrencyLimiter
  • Illuminate\Redis\Limiters\ConcurrencyLimiterBuilder

Redisあんまり詳しく無いのですが、redis操作する際にコマンドを打つのを、一連のまとめた処理として、luaスクリプトで実行できるようです。
RDBで言えば、ストアドプロシージャのようなものでしょうか?
php上にコードとして定義されているので、Redis側に登録済みの状態で実行されるのではなく、都度スクリプト自体をRedis上で実行しているっぽいので、厳密には違うでしょう。

具体的にはevalというコマンドを使っています。
ドキュメントは以下を見るとよさそうです。
https://redis.io/commands/eval

funnelで内部的に呼び出しているluaのscriptは以下の2つです。

lockScript

for index, value in pairs(redis.call('mget', unpack(KEYS))) do
    if not value then
        redis.call('set', KEYS[index], ARGV[3], "EX", ARGV[2])
        return ARGV[1]..index
    end
end

releaseScript

if redis.call('get', KEYS[1]) == ARGV[1]
then
    return redis.call('del', KEYS[1])
else
    return 0
end

と、書いたものの、Luaは大昔にProgramming in Luaを読んだことがある程度なので、まじでわかりません。
察するにlockScriptについては、特定のキーがなければ、特定の期限つきのレコードを登録し、登録した結果を返す。

releaseScriptについては、特定のキーに値があり、それが特定の値と一致するのであれば、それを消すという機能を持つようです。
詳しくは、コード読んでください。

上記のコードから推察するに、実行開始時にRedisにキーを登録し、それが登録されている状態では、同時に同じ処理が走らないように制御できる。
また実行が終わったら、キーを削除し、処理を実行可能な状態に戻すことができる。というもののようです。

では、それを実際に試してみたいと思います。

挙動

funnelはlimit,releaseAfter,blockという関数で、動きを制御することができるのですが、それぞれ見ていきたいと思います。
こんな感じのコードを使っていきます。

<?php
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Redis\LimiterTimeoutException;
use Illuminate\Support\Facades\Redis;

class LaravelRedisTest implements ShouldQueue
{
    /** @var int */
    public $tries = 1;

    /** @var string */
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function handle()
    {
        $redis = Redis::connection();
        $redis->funnel(self::class)
            ->limit(2)
            ->block(0)
            ->then(function () {
                sleep(3);
                logger("{$this->name} is success");
            }, function (LimiterTimeoutException $e) {
                logger("{$this->name} is failed");
            });
    }
}

limit

まずはlimitです。これは同時にいくつ実行できるかを指定します。
同時にblockも使っていますが、これはあとで説明します。

以下の処理をした際の期待値を先に書いておきます。
limitを指定することで、指定数までの処理は実行でき、それを超えると失敗することがわかります。

  1. 処理A(3秒)を実行 -> 成功
  2. 処理B(3秒)を実行 -> 成功
  3. 処理C(3秒)を実行 -> 失敗
  4. 処理Aを終了(3秒まつ)
  5. 処理D(3秒)を実行 -> 成功

実行スクリプト

function run()
{
    dispatch(new LaravelRedisTest('A'));
    dispatch(new LaravelRedisTest('B'));
    dispatch(new LaravelRedisTest('C'));
    sleep(3);
    dispatch(new LaravelRedisTest('D'));
}

実行部分

$redis = LaravelRedis::connection();
$redis->funnel(self::class)
    ->limit(2)
    ->block(0)
    ->then(function () {
        sleep(3);
        echo "{$this->name} is success" . PHP_EOL;
    }, function (LimiterTimeoutException $e) {
        echo "{$this->name} is failed" . PHP_EOL;
    });

結果

[2020-12-12 15:56:19] local.DEBUG: C is failed  
[2020-12-12 15:56:21] local.DEBUG: A is success  
[2020-12-12 15:56:21] local.DEBUG: B is success  
[2020-12-12 15:56:25] local.DEBUG: D is success  

releaseAfter

releaseAfterに値を指定すると、その時間が立つとロックを解除して、他の処理を実行できるようになるようです。
なので、長い処理が動く際は、気をつけて指定する必要がありそうです。
またdefaultでは60秒なので指定しなければ60秒立つと勝手にロックが解除されます。

手順と期待値です。
releaseAfterを指定することで、実行中であってもロックが解除されて、実行可能になっていることがわかります。

  1. 処理A(3秒)を実行
  2. 処理B(3秒)を実行
  3. 1秒待つ
  4. 処理C(3秒)を実行 -> 成功
function run()
{
    dispatch(new LaravelRedisTest('A'));
    dispatch(new LaravelRedisTest('B'));
    sleep(1);
    dispatch(new LaravelRedisTest('C'));
}
$redis = LaravelRedis::connection();
$redis->funnel(self::class)
    ->limit(2)
    ->releaseAfter(1)
    ->block(0)
    ->then(function () {
        sleep(3);
        echo "{$this->name} is success" . PHP_EOL;
    }, function (LimiterTimeoutException $e) {
        echo "{$this->name} is failed" . PHP_EOL;
    });
[2020-12-12 16:10:52] local.DEBUG: A is success  
[2020-12-12 16:10:52] local.DEBUG: B is success  
[2020-12-12 16:10:53] local.DEBUG: C is success  

block

blockは、ロックが取れない場合に、どれくらい待つかという感じです。
これがdefaultが3秒になっているので、何も指定しなければ、3秒待つ形になります。
limitやrelaseAfterの項でblockに0を指定していたのは、挙動が複雑になるためでした。

手順と期待値です。
blockを指定することで、ロックが解除されるのを待っているのがわかります。

  1. 処理A(3秒)を実行
  2. 処理B(3秒)を実行 -> 成功
  3. 処理C(3秒)を実行 -> 成功
function run()
{
    dispatch(new LaravelRedisTest('A'));
    dispatch(new LaravelRedisTest('B'));
    dispatch(new LaravelRedisTest('C'));
}
$redis = LaravelRedis::connection();
$redis->funnel(self::class)
    ->limit(2)
    ->block(3)
    ->then(function () {
        sleep(3);
        echo "{$this->name} is success" . PHP_EOL;
    }, function (LimiterTimeoutException $e) {
        echo "{$this->name} is failed" . PHP_EOL;
    });
[2020-12-12 16:16:03] local.DEBUG: A is success  
[2020-12-12 16:16:03] local.DEBUG: B is success  
[2020-12-12 16:16:06] local.DEBUG: C is success  

throttle

throttleはfunnelと違い、時間の概念が追加されます。
単位時間に、いくつの処理を同時に実行するか制御することができるようです。

実装は以下のクラスに存在します。

  • Illuminate\Redis\Limiters\DurationLimiter
  • Illuminate\Redis\Limiters\DurationLimiterBuilder

redis script

それではRedisに投げているLuaスクリプトを読んで見ましょう。

local function reset()
    redis.call('HMSET', KEYS[1], 'start', ARGV[2], 'end', ARGV[2] + ARGV[3], 'count', 1)
    return redis.call('EXPIRE', KEYS[1], ARGV[3] * 2)
end
if redis.call('EXISTS', KEYS[1]) == 0 then
    return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1}
end
if ARGV[1] >= redis.call('HGET', KEYS[1], 'start') and ARGV[1] <= redis.call('HGET', KEYS[1], 'end') then
    return {
        tonumber(redis.call('HINCRBY', KEYS[1], 'count', 1)) <= tonumber(ARGV[4]),
        redis.call('HGET', KEYS[1], 'end'),
        ARGV[4] - redis.call('HGET', KEYS[1], 'count')
    }
end
return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1}

funnelは2つスクリプトがありましたが、throttleは1つだけです。
ARGVとか、KEYSとか変数が色々あってもうわからんですね。
それらの変数はphpのコードを読めば分かるのですが、それはコードをご自身で読んでください。

簡単に説明すると、上記のreset関数が、有効期限つきで、登録時と削除時と、同時実行数を1として登録しています。
特定のキーが存在するとき、または存在していても有効期限外であれば、reset関数でキーを登録する。
対象のキーが存在し、その有効期限内であれば、新しくキーを登録したり更新はしない。
返却する値は、新しく実行可能か、現在いくつの処理を並行で処理しようとしているか、また現在の有効期限です。

単位時間に実行する数を制限してくれそうな雰囲気がありますね。
ただ、任意の期間に対して同時に実行している数を制御する、という形ではなさそうです。
あくまで、特定のキーに対して実行しているものが無い際に、最初に実行されたタイミングから指定時間は、同時実行数を制限してくれる。
というものの用に感じます。

throttleの挙動

funnelと違い、指定する部分の違いではなく、時間が立つと自動的に再実行できるという部分から検証していこうと思います。

処理時間が短い場合

まずは、処理時間が3秒と、指定時間に対して短い、あるいは同等の場合の挙動を見ておきます。
これはわかりやすいはず。

以下、手順と期待値です。
3秒間の間に、2つ以上実行されれば、失敗するし、3秒立てば成功するのが確認できます。

  1. 処理A(3秒)を実行
  2. 処理B(3秒)を実行
  3. 処理C(3秒)を実行 -> 失敗
  4. 処理Aを終了(3秒まつ)
  5. 処理D(3秒)を実行 -> 成功
function run()
{
    dispatch(new LaravelRedisTest('A'));
    dispatch(new LaravelRedisTest('B'));
    dispatch(new LaravelRedisTest('C'));
    sleep(3);
    dispatch(new LaravelRedisTest('D'));
}
$redis = LaravelRedis::connection();
$redis->throttle(self::class)
    ->allow(2)
    ->every(3)
    ->block(0)
    ->then(function () {
        sleep(3);
        echo "{$this->name} is success" . PHP_EOL;
    }, function (LimiterTimeoutException $e) {
        echo "{$this->name} is failed" . PHP_EOL;
    });
[2020-12-12 16:32:56] local.DEBUG: C is failed  
[2020-12-12 16:32:59] local.DEBUG: A is success  
[2020-12-12 16:32:59] local.DEBUG: B is success  
[2020-12-12 16:33:01] local.DEBUG: D is success  

処理時間が長い場合

次は、指定時間に対して、処理の時間が長い場合の挙動を見てみます。

以下が手順と期待値です。
短い場合と結果は変わりませんが、最初の処理がおわっていなくても、指定時間が経過すれば、実行可能になっていることがわかります。

  1. 処理A(10秒)を実行
  2. 処理B(10秒)を実行
  3. 処理C(10秒)を実行 -> 失敗
  4. 3秒まつがAもBも終わっていない
  5. 処理D(10秒)を実行 -> 成功
function run()
{
    dispatch(new LaravelRedisTest('A'));
    dispatch(new LaravelRedisTest('B'));
    dispatch(new LaravelRedisTest('C'));
    sleep(3);
    dispatch(new LaravelRedisTest('D'));
}
$redis = LaravelRedis::connection();
$redis->throttle(self::class)
    ->allow(2)
    ->every(3)
    ->block(0)
    ->then(function () {
        sleep(10);
        echo "{$this->name} is success" . PHP_EOL;
    }, function (LimiterTimeoutException $e) {
        echo "{$this->name} is failed" . PHP_EOL;
    });
[2020-12-12 16:35:57] local.DEBUG: C is failed  
[2020-12-12 16:36:06] local.DEBUG: A is success  
[2020-12-12 16:36:06] local.DEBUG: B is success  
[2020-12-12 16:36:10] local.DEBUG: D is success  

まとめ

わたしが実際に仕事で使ったのはthrottleの方ですが、funnelと比べて仕掛けや挙動がわかりづらく迷いました。
よかったら使ってみてください。

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

【導入まで】Laravel x Vue.js(laravel/ui)

Laravel x Vue.js (laravel/ui)

はじめに

Laravelの導入については、DockerやXAMPPなどを利用するなど様々な方法がありますが、ここではLaravelの導入が完了しているという体で話をします。

本記事で使用する主要なcomposerのパッケージは、laravel/uiでありサポート状況は以下の様になっています。

laravel/uiサポートバージョン(2020/12/11時点 *1参照)

laravel/uiのバージョン Laravelのバージョン
1.x 5.8,6.x
2.x 7.x
3.x 8.x

動作環境

・OS: Mac OS Catalina
・PHP: 7.4.13
・Laravel: 8.18.1
・composer
・npm

導入手順

laravel/uiパッケージをインストール

composer require laravel/ui

上記コマンドを実行し、laravel/uiをインストールします。

基本的なvueの構成を生成する

php artisan ui vue

*おまけ:この時に php artisan ui laravel/ui --authと打ち込むとログイン/レジスター機能を盛り込むことが出来ます

npmパッケージ

npm install

上記コマンドを実行する。

アセットをコンパイル

npm run dev

参考

1: https://github.com/laravel/ui

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

自社サービスのインタラクションデザインを改善するためにPHPで非同期処理をしてみた

はじめまして、こんにちは。
今年の10月に福岡で起業して、なんちゃってCTOをしている赤と黒が好きな若造です。
今回は開発中の自社サービスのインタラクションデザインを改善するためにPHPで非同期処理をしてみたという話を書きます。

PHPで処理を並行したい。

ユーザーがあるアクションを起こした時に、PHPでの処理に10秒掛かるとします。
その間ユーザーは10秒待つ羽目になるわけですが、この時間が貴重な現代社会に置いて、ユーザーは10秒待つと当然イライラします。
image.png
なのでなるべく早くリクエストからレスポンスの時間を短くしたいわけですが、いくらPHP8で爆速になったとは言え、限界もあります。
そこで「重要な処理」と「重要では無い処理」で処理自体を分割して
「重要な処理」が終わった時点でレスポンスを返し、「重要では無い処理」は裏で並列で処理するアイディアを思いつきました。
image.png
これなら実質3秒でユーザーにレスポンスを返す事ができます!

PHPはシングルスレッドだよ

ですが残念なことにPHPは基本的にシングルスレッドで、並列処理は出来ません。1
だから無理です。
今日のアドベントカレンダー記事は以上です。あざした。

image.png
 

 

…というわけにはいきません。
PHPはシングルスレッドですが、マルチプロセスは可能です。
「並列処理」ではなくて「分割処理」という言葉のほうがイメージしやすいですね
image.png
シングルスレッドとマルチプロセスは結果は同じ様に見えますが、プロセスが分割されているので、変数などの情報を共有できないのは注意しなくてはいけません。
(正確にはメモリが共有出来ない)

ではどうやってプロセスを分けるのか、を解説していきます。

exec関数

詳しい解説はPHPの公式のマニュアルに任せます。
PHPマニュアル:https://www.php.net/manual/ja/function.exec.php

PHP 7.4.10の環境で以下のコードを書いてみました。
exec関数で「返り値を/dev/nullに捨てて、バックグラウンド処理する、phpのプロセス」を立ち上げるコマンドを叩くコードです。

index.php
<?php
    echo "START";
    error_log("\n\n".date('Y-m-d H:i:s')."  START", 3, __DIR__.'/log.txt');

    exec('php '.__DIR__.'/num1_wait5.php > /dev/null &');
    exec('php '.__DIR__.'/num2_wait1.php > /dev/null &');
    exec('php '.__DIR__.'/num3_wait10.php > /dev/null &');
    exec('php '.__DIR__.'/num4_wait0.php > /dev/null &');

    error_log("\n".date('Y-m-d H:i:s')."  END", 3, __DIR__.'/log.txt');
    echo "END";
num1_wait5.php
<?php
    sleep(5);
    error_log("\n".date('Y-m-d H:i:s')."  NUM1_WAIT5", 3, __DIR__.'/log.txt');
num2_wait1.php
<?php
    sleep(1);
    error_log("\n".date('Y-m-d H:i:s')."  NUM2_WAIT1", 3, __DIR__.'/log.txt');
num3_wait10.php
<?php
    sleep(10);
    error_log("\n".date('Y-m-d H:i:s')."  NUM3_WAIT10", 3, __DIR__.'/log.txt');
num4_wait0.php
<?php
    error_log("\n".date('Y-m-d H:i:s')."  NUM4_WAIT0", 3, __DIR__.'/log.txt');

index.phpで4つのプロセスを立ち上げて処理するコードです。
実行結果は以下になります。

2020-12-12 10:28:00  START
2020-12-12 10:28:00  END
2020-12-12 10:28:00  NUM4_WAIT0
2020-12-12 10:28:01  NUM2_WAIT1
2020-12-12 10:28:05  NUM1_WAIT5
2020-12-12 10:28:10  NUM3_WAIT10

図にするとこんな感じです。
image.png

本来であれば全ての処理をシングルプロセスで処理すると完了に5+1+10+0=16秒掛かるところを
マルチプロセスにしたおかげで、全ての処理の完了に10秒で済んでしまいました。

しかしexec関数はlinuxのコマンドを叩く処理で、マルチプロセスのためのものではありません。
linuxのコマンドを叩くという性質上、うっかりユーザーの入力をそのままexec関数の引数に当てるなんてことは絶対にしないようにしましょう。

exec関数でのマルチプロセスの欠点

一見簡単にマルチプロセスで処理できるexec関数ですが、実は弱点があります。
処理を分割するためにプロセスを作るという性質上、処理が立て込んだ時には処理が横に広がりすぎて、サーバーに影響を及ぼすからです。
最悪サーバーが落ちると思います。
image.png

Laravelでの非同期処理

素のPHPでマルチプロセスをしてみましたが、実際の業務やサービスではフレームワークを使うことでしょう。
株式会社ナインステクノロジーズはLaravelが得意なので、Laravelでも非同期処理をやってみましょう。

Laravelではexec関数でのマルチプロセスとは少し違って「ジョブとキュー」で並列処理を実現します。

ジョブとキュー

厳密に言えば「ジョブとキュー」もマルチプロセス処理の一種で
「ジョブ」と言われる処理の塊を、「キュー」と呼ばれるプロセスが変わって処理するイメージです。

image.png
最初ジョブは全く無く、キューもジョブが無いので待機状態になっています。

image.png
なんらかのアクションでジョブが記録されると、キューはそれに気づきます。

image.png
キューは記録されているジョブを1つ取り出して処理を始めます。

こういう流れで処理を非同期的に処理します。

php artisan queue

詳しい解説はLaravelのドキュメントを翻訳しているサイトに任せます。
Laravel 6.x キュー:https://readouble.com/laravel/6.x/ja/queues.html

Laravel Framework 6.20.6の環境でジョブとキューの設定をやっていきます。
まずジョブをデータベースに記録したいので、migrationを行います。

php artisan queue:table
php artisan queue:failed-table    #初期インストール状態では既にmigrationファイルは存在しているのでエラーが出る。
php artisan migrate

これでjobsテーブルfailed_jobsテーブルが作られます。

そしてjobファイルを生成して、書いていきます。

php artisan make:job Num1Wait5.php
php artisan make:job Num2Wait1.php
php artisan make:job Num3Wait10.php
php artisan make:job Num4Wait0.php
/app/Jobs/Num1Wait5.php
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class Num1Wait5 implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        sleep(5);
        error_log("\n".date('Y-m-d H:i:s')."  NUM1_WAIT5", 3, '/log.txt');
    }
}
/app/Jobs/Num2Wait1.php
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class Num2Wait1 implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        sleep(1);
        error_log("\n".date('Y-m-d H:i:s')."  NUM2_WAIT1", 3, '/log.txt');
    }
}
/app/Jobs/Num3Wait10.php
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class Num3Wait10 implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        sleep(10);
        error_log("\n".date('Y-m-d H:i:s')."  NUM3_WAIT10", 3, '/log.txt');
    }
}
/app/Jobs/Num4Wait0.php
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class Num4Wait0 implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        sleep(0);
        error_log("\n".date('Y-m-d H:i:s')."  NUM4_WAIT0", 3, '/log.txt');
    }
}

そしてジョブを入れるcontrollerファイルを生成して、書いていきます。

php artisan make:controller TestController
/app/Http/Controllers/TestController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TestController extends Controller
{
    public function test(){
        echo "START";
        error_log("\n\n".date('Y-m-d H:i:s')."  START", 3, '/log.txt');

        $this->dispatch(new \App\Jobs\Num1Wait5() );
        $this->dispatch(new \App\Jobs\Num2Wait1() );
        $this->dispatch(new \App\Jobs\Num3Wait10() );
        $this->dispatch(new \App\Jobs\Num4Wait0() );

        error_log("\n".date('Y-m-d H:i:s')."  END", 3, '/log.txt');
        echo "END";
    }
}

そして.envQUEUE_CONNECTIONを編集します。

.env
QUEUE_CONNECTION=database

最後にキューのプロセスを立ち上げます。

php artisan queue:work > /dev/null &

これで準備完了です。
実際にTestController@testを実行した結果が以下です。

2020-12-12 12:58:00  START
2020-12-12 12:58:00  END
2020-12-12 12:58:05  NUM1_WAIT5
2020-12-12 12:58:06  NUM2_WAIT1
2020-12-12 12:58:16  NUM3_WAIT10
2020-12-12 12:58:16  NUM4_WAIT0

…あれ?

php artisan queueでのジョブとキューでの欠点

先程の実行結果は、16秒掛かってしまいました。
exec関数では10秒だったのになぜでしょうか?

実は先程の実行は「キューが1個だったから」16秒掛かったのです。

image.png
キューが1個なので以下の流れで処理をします。

ジョブ1を処理して、
ジョブ1が終わったらジョブ2を処理して、
ジョブ2が終わったらジョブ3を処理して、
ジョブ3が終わったらジョブ4を処理して、
ジョブ4が終わる。

キューは1個なので、処理できるジョブも当然1個です。だから並列的に処理が出来ないんですね。
なので、並列的に処理したい場合はキューの数を増やすと良いでしょう。

image.png
キューを増やすことで同時に処理できるジョブの数が増え、効率的に処理をこなすことが出来ます。

キューの数だけジョブを捌けるのですが、ここにも落とし穴があって
例えばジョブの増加速度よりもキューがジョブを捌く速度が遅くなってしまうと
いつまで立っても処理が開始されないジョブが出てきてしまう可能性があります。
image.png

まさに炎上状態。
一応Laravelではジョブの優先度やキューへの振り分けを操作出来ますが、万が一こういう自体が起こらない訳ではありません。

まとめ

PHPはシングルスレッドで並列処理は難しいですが
exec関数やLaravelのqueueで非同期的に処理を実行できます。

活用することでユーザーへのレスポンスが早くなることでしょう。
本当はこの処理と合わせてプログレスバーを実装する予定でしたが、以外と長くなったので今度にします。

もし間違っているところがあればマサカリお願いします。(言葉尻を捉えてイジメるのはやめてくださぃ。

明日は奇しくも同じ福岡の企業に所属するせいけしろーさんです。

ココまで読んでくださってありがとうございました。
LGTMとTwitterをフォローしてくれると嬉しいです!よろしくおねがいします!!!
@9th_tech_Ryo

あと別のアドベントカレンダーになりますが、弊社長も記事を明日書くので、ぜひ見てみてください!!!!!
https://qiita.com/akira_9th


  1. ptreadsというモジュールを使えば可能らしい 

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

Laravel 5.5 Storageファサードで画像ファイルを保存

URLから取得した画像ファイルをStorageファサードを使い
storage/app/public/下に保存した時に、少し詰まったのでメモ。

Storageファザードを使ってファイルを保存する方法

Storageファザードを使ってファイルを保存する方法は以下
公式リファレンスより抜粋
https://readouble.com/laravel/5.5/ja/filesystem.html

readouble.com/laravel/5.5/ja/filesystem.html
use Illuminate\Support\Facades\Storage;

Storage::put('file.jpg', $contents);

Storage::put()を使うと、第一引数にファイル名、第二引数に保存したいファイルを入れます。
そうすると通常はstorage/app/public/内に画像が保存されます。

でも、保存先フォルダも指定したい!
という場合はこちら

readouble.com/laravel/5.5/ja/filesystem.html
use Illuminate\Http\File;
use Illuminate\Support\Facades\Storage;

// 自動的に一意のIDがファイル名として指定される
Storage::putFile('photos', new File('/path/to/photo'));

// ファイル名を指定する
Storage::putFileAs('photos', new File('/path/to/photo'), 'photo.jpg');

引数はそれぞれ以下の通りです。
Storage::putFile(保存先, new File('ファイルのパス'));
Storage::putFileAs('保存先', new File('ファイルのパス'), ファイルの名前);

今回はファイル名も指定したい、保存先も指定したい、とのことで、
Storage::putFileAs()を使うことにしました。

詰まったところ。 ファイルのパスって?

Illuminate\Http\Fileのインスタンスの引数にファイルのパス指定しますが、
ファイルのパスの取り方が思いつかなくて少し考えました。

が、簡単にできました。

一時ファイルを使います。

//画像のURL
$url = 'https::example.com/img.jpg';
//URLからファイル名を取得 ここはお好きな方法でファイル名を決めてください。
$file_name = substr(strrchr($url,"/"),1);
//URLからファイル取得
$img_downloaded = file_get_contents($url);
//一時ファイル作成
$tmp = tmpfile();
//一時ファイルに画像を書き込み
fwrite($tmp, $img_downloaded);
//一時ファイルのパスを取得
$tmp_path = stream_get_meta_data($tmp)['uri'];
//storageに保存。
Storage::putFileAs('images', new File($tmp_path), $file_name);
//一時ファイル削除
fclose($file_handle);

ちゃんとstorage/app/public/images/下に保存できました!
publict直下に保存したい時は

Storage::putFileAs('', new File($tmp_path), $file_name);

フォルダの指定を空にすればOKです!

S3などのクラウドディスクにファイルを保存する場合もこの方法で大丈夫なのではないかとおもいます!

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

共同開発で学んだこと

私立探求学園というオンラインサロンで技求祭が行われました。
今回は共同でのアプリ開発となりました。

1.技求祭とは

技術を探求するお祭り(ハッカソン)です。
テーマ・・・技術祭のテーマはコロナの中で便利に使えるアプリ
期間・・・5月初旬から8月末
白組、青組、紅組に分かれあるテーマをもとにアプリを共同開発しました。
私が所属している白組ではLaravel,Vue.jsでアプリを開発しました。

2.メンバー構成(白組)

PM 1人
Laravelチーム 4人
Vue.jsチーム  4人

3.開発工程

①アプリ作成案
②画面遷移図
③デザインカンプ
④環境構築(Laravel 5.8 Vue.js 2.6)
⑤ER図
⑥開発

■4.アプリ作成案
白組ではOTOSHIという料理アプリを開発することにしました。

■5.OTOSHIの機能
・秒単位から10分以内で作れる料理のみに厳選
・オンライン飲み会でたべるおつまみ
・皆で同じメニューをつくり、いいねの数で競い合う

6.画面遷移図

どんな画面があって、どのようにいったりきたりできるのか表す図
白組アプリのサイトマップの叩き台 (1).png

7デザインカンプ

画面のデザイン完成図!
iOS の画像 (8).png
iOS の画像 (7).png
iOS の画像 (2).png
iOS の画像 (3).png
iOS の画像 (6).png

8.ER図

どんな画面があって、どのようにいったりきたりできるのか表す図
200621_ER図.png

9.開発

Laravelチーム、Vueチームに分かれて開発。
勉強会を6月末まで1週間に1回行い、7月からタスクを割り当て本格的に開発に着手。

10.発表

8月末に探求学園内(オンライン)て各組発表を行いました。
白組はそこで見事優勝することができました!

発表で使われた資料

技求祭

OTOSHIアプリ完成品の動画

https://www.youtube.com/watch?v=nVFrVWLM-fg
IMAGE ALT TEXT HERE

11.技求祭で学んだこと

・開発工程
・アイディアを形にする楽しさ
・共同開発することの難しさ

今回を通して自分たちでアイディアをだし、ものを作ることの楽しさを知ることができたのが一番の学びです。
ああでもない、こうでもないといいながらアイディアを形にするのはとても楽しかったです!
エンジニアになれて本当に良かったと思いました。

12.反省点

・チームとしてタスクの進捗管理の見える化をしていなかった
・誰がどれくらいの時間開発に時間を割くことができるのか事前に把握していなかった
・皆で共有できるきちんとした議事録がなかった

13.総括

まだまだ未熟もので、なかなか要件定義の部分から仕事で関わることが難しい中、環境と機会を与えてくれた私立探求学園は控えめに言っても最高だと思いました!
これからも私立探求学園で技術を探求し続けていきたいと思いました。
今回チームとしてアプリを作りましたが、技求祭を通して学んだことを活かして個人でアプリ草案から開発まで行いたいと思います。

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

Composerのバージョンを最新にアップデートする方法

環境

macOS Catalina(10.15.5)

現在のバージョンを確認

ターミナル
$ composer --version
Composer version 1.9.0 2019-08-02 20:55:32

現在のバージョンが1.9.0だったので、これを最新バージョンにする。

最新バージョンのcomposerをインストールする

ホームディレクトリで下記を実行

ターミナル
$ sudo curl -sS https://getcomposer.org/installer | php

(ユーザーアカウントのパスワードを入力)

------------ 下記が表示される ------------
All settings correct for using Composer
Downloading...

Composer (version 2.0.8) successfully installed to: /home/yourname/composer.phar
Use it: php composer.phar

現在のバージョンから最新バージョンに上書きする

作成されたcomposer.pharを、composerディレクトリに移動することで、最新バージョンに上書きされます。

ターミナル
$ sudo mv composer.phar /usr/local/bin/composer

最新バージョンになっているかを確認

再度下記コマンドでバージョンを確認

ターミナル
$ composer --version
Composer version 2.0.8 2020-12-03 17:20:38

(記事投稿時点での最新バージョンは2.0.8でした)

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

PHP(laravel)でUndefined property: stdClassのメッセージが出たとき

PHPのstdClassは簡単に言うと、プロパティやメソッドが無いオブジェクトです。

その特徴を活かして使われることもありますが、
意図せず「Undefined property: stdClass」のメッセージが表示された場合は
定義していないオブジェクトを使っている可能性があります。

//クラス
class Aaa {
 public $word;
}
//オブジェクト
$a = new Aaa;//定義
$a->word = 'こんにちは';
echo $a->word; //出力:こんにちは

//定義していない変数$bを使うと
$b->word = 'こんにちは';
echo $a->word; //出力:Undefined property: stdClass

私の場合はlaravelでの開発中に意図せず出てきたので、
使っている変数を確かめてみると、メッセージが解消できました。

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