20200908のPHPに関する記事は14件です。

CakePHP3のfindでcontain要素に検索条件を追加する

例えばUsersテーブルにUserInfoテーブルが紐付いていて、prefecture_id=2で名字が佐藤か工藤の1名をFindしたいときは下記のようになります。

useで$prefecture_idをわたしてあげるのがミソです。

UsersController.php
$prefecture_id = 2;
$user = $this->Users->find()->contain(
    ['UserInfo' => function($q) use ($prefecture_id) {
        return $q->where(['UserInfo.prefecture_id' => $prefecture_id]);
    }])
    ->where([
        'Users.last_name IN' => ['佐藤', '工藤']
    ])
    ->first();

かんたんですね。

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

PHP・GCの話-4話)MemoryLeakと解除できない変数データ

前書き

  • すべての記事は、自分の勉強目的と主観の整理を含めています。あくまで参考レベルで活用してください。もし誤った情報などがあればご意見をいただけるととっても嬉しいです。
  • 内容では、省略するか曖昧な説明で、わかりづらいところもあると思います。そこは、連絡いただければ補足などを追加するので、ぜひ負担なくご連絡ください。
  • 本文での「GC」は、「Garbage Collection, Garbage Collector」の意味しており、略語として使われています。
  • この記事は、連載を前提に構成されています。

※ 連載目録

※ 連載で使うサンプルコード

Sample Code Link on Github

● ExampleGc.php : 2話から6話までの内容で使うサンプルコードです。
● ExampleWeakReference : 7話のWeakReferenceの内容で使うサンプルコードです。

本連載記事は、基本的にこのサンプルコードをベースに説明をしています。
サンプルコードは、必ず見る必要も実行してみる必要もありません。
各話ごとに、コードを分解して動作原理と結果を解説しますので、基本記事の内容で足りるように心がけます。
あくまで、全体コードをみたい、手元で回してみたい、修正して回してみたいという方向けです。

今回の話

今回は、以下のものを話そうと思うます。

  • 1. Memory Leakとは?
  • 2. 解除できない変数データの例 (循環参照)
  • 3. Summary

今回からは、本格的にサンプルコードを引用しながら、見ていきますので、
以下のリンクのコードを一緒に参考にしながら見ると良いと思います。

1. Memory Leakとは?

シンプルに言うと、

もう使えない変数データが、メモリを専有し続ける現象

ですね。これを「ゴミ・Garbage」と表現したりもします。

もうちょっと詳しく説いて行くために、Memory Leakの定義をwikiから引用すると、

https://en.wikipedia.org/wiki/Memory_leak#cite_note-1

① In computer science, a memory leak is a type of resource leak that occurs when a computer program incorrectly manages memory allocations in such a way that memory which is no longer needed is not released.
② A memory leak may also happen when an object is stored in memory but cannot be accessed by the running code.

少し意訳すると

① コンピューター工学において、Memory Leakとは、プログラムで、もう使わないのに解除されないような、間違ったメモリ空間の割当により起きる、有限な資源の無駄遣いの現象です。
② 一例として、memory leakは、メモリに保存はされているものの、実行コード上ではこれ以上サクセスされないオブジェクトにより起きることもありえます。

ですね。

それ以外で一番多い事例は、ネットワークコネクションやグラフィックなどのresourceタイプの解除を忘れることだと思います。※1

②に対するMemory Leakの一つの例が、今から見ていく「循環参照」です。
引き続き、②の事例の「循環参照」に関して詳しく見ていきます。

2. 解除できない変数データの例

ここでの「解除できない」というのは、

プログラマーの意図的には、すでに解除しており、これ以上使われるはずのない変数の元データがメモリ上にの残り続けること

を意味します。

簡単な例えとしては、オブジェクト同士の「循環参照」があります。
簡単なコードで紹介すると以下のコードで再現できます。

$a = new \stdClass;
$b = new \stdClass;

//循環参照
$a->node = $b;
$b->node = $a;

//これ以上使われないはずなので、このケースは、メモリからデータは消滅しない
unset($a);
unset($b);

これがなぜ、問題になるのかを、サンプルコードと実行結果で解説します。

1) サンプルコード例

● クラス定義引用
Sample Code Link on Github

abstract class Base
{
    private $dummyData;
    private $tag = null;
    private $nodes = array();

    public function __construct($tag)
    {
        $this->tag = $tag;
        $this->dummyData = str_repeat('a', 20*1024*1024); //20M Byte size approximately
    }

    /**
     * add Reference as ChildNode
     */
    public function addNode(object $obj)
    {
        $this->nodes[] = $obj;
        return $this;
    }
}

class AliveInScope extends Base {}

class CircularReference extends Base {}

● コード引用
Sample Code Link on Github

    private function doExampleGcBasic()
    {
//...中略
        Log::debug(null, ['event' => 'new', 'msg' => 'V']);
        $alive = new AliveInScope('V');
//...中略
        Log::debug(null, ['event' => 'new', 'msg' => 'A, B']);
        $circleA = new CircularReference('A');
        $circleB = new CircularReference('B');

        Log::debug(null, ['event' => 'set', 'msg' => '$alive`s reference to A']);
        $circleA->addNode($alive);

        Log::debug(null, ['event' => 'set', 'msg' => 'circluar reference on A B']);
        $circleA->addNode($circleB);
        $circleB->addNode($circleA);

        xdebug_debug_zval('alive');
        xdebug_debug_zval('circleA');
        xdebug_debug_zval('circleB');
        $this->logMemUsage();

        Log::debug(null, ['event' => 'unset', 'msg' => 'A, B']);
        unset($circleA);
        unset($circleB);

        xdebug_debug_zval('alive');
        xdebug_debug_zval('circleA');
        xdebug_debug_zval('circleB');
        $this->logMemUsage();
//...中略

● 実行結果引用

root@bc290870f5e9:/var/www/html/subdomain/laravel# ./artisan example:gc | cut -d "$" -f 1

[2020-09-07 20:30:37] local.DEBUG:  {"event":"new","msg":"V"}
alive: (refcount=1, is_ref=0)=class App\Console\Commands\AliveInScope { private 
[2020-09-07 20:30:37] local.DEBUG:  {"Memory Usage(Bytes)":"37,060,552"} 
[2020-09-07 20:30:37] local.DEBUG:  {"event":"new","msg":"A, B"}
[2020-09-07 20:30:37] local.DEBUG:  {"event":"set","msg":"
[2020-09-07 20:30:37] local.DEBUG:  {"event":"set","msg":"circluar reference on A B"}
alive: (refcount=2, is_ref=0)=class App\Console\Commands\AliveInScope { private 
circleA: (refcount=2, is_ref=0)=class App\Console\Commands\CircularReference { private 
circleB: (refcount=2, is_ref=0)=class App\Console\Commands\CircularReference { private 
[2020-09-07 20:30:37] local.DEBUG:  {"Memory Usage(Bytes)":"79,015,032"} 
[2020-09-07 20:30:37] local.DEBUG:  {"event":"unset","msg":"A, B"}
alive: (refcount=2, is_ref=0)=class App\Console\Commands\AliveInScope { private 
circleA: no such symbol
circleB: no such symbol
[2020-09-07 20:30:37] local.DEBUG:  {"Memory Usage(Bytes)":"79,015,672"} 

2) コードと実行結果の解説

        Log::debug(null, ['event' => 'set', 'msg' => 'circluar reference on A B']);
        $circleA->addNode($circleB);
        $circleB->addNode($circleA);
[2020-09-07 20:30:37] local.DEBUG:  {"event":"set","msg":"circluar reference on A B"}
...中略
[2020-09-07 20:30:37] local.DEBUG:  {"Memory Usage(Bytes)":"79,015,032"} 

ソスコード上の上記のポイントで、AとBは、お互いの内部でお互いを参照することになります。
その後のメモリの使用量は「79,015,032」Bytesになっています。

        Log::debug(null, ['event' => 'unset', 'msg' => 'A, B']);
        unset($circleA);
        unset($circleB);
[2020-09-07 20:30:37] local.DEBUG:  {"event":"unset","msg":"A, B"}
alive: (refcount=2, is_ref=0)=class App\Console\Commands\AliveInScope { private 
circleA: no such symbol
circleB: no such symbol
[2020-09-07 20:30:37] local.DEBUG:  {"Memory Usage(Bytes)":"79,015,672"} 

unsetをして、A,Bは、これ以上使うこともできないのに、なぜかメモリの使用量は減っていません。
つまり、使うこのもないのに、有限であるメモリ空間をずっと専有していることになります。

なぜ、メモリの使用量は減らずに、実のデータがずっと残り続けるのでしょう。

3) コードの実行時に起きる変数と参照カウントの変化解説(GIF)

①循環参照変数の生成の段階の変化

上記のイメージの4番目のように、AとBは、$circleA, $circleBの参照以外に、各自の内部で、お互いを参照するようになります。
だとしたら、$circleA$circleBをunsetし、変数を無効にしたらどうなるのでしょう。

$circleA$circleBをunsetした後の段階の変化 (絵が間違っている)

前回の「変数が消滅しない条件」は

「実際のデータの参照が一つ無効になる」時、参照カウント(refcount)が「1以上」であれば、データは消滅せずに残り続ける。

でした。

絵の2番めのように、$circleA$circleBの変数を解除したことで、2つとも実のデータにアクセスできなくなりました。
しかし、元のデータはお互いを参照しているので、参照カウントは2→1になりますが、その時の参照カウントが1以上であるため、「どこかで使われている」とシステムでは認識し、メモリから解除することはできなくなります。

なので、プログラマー的には、消滅してほしいデータであるにも関わらず、ずっとメモリに残り続けることになります。

まさに、Memory Leakとして話した、もう使えない変数データが、メモリを専有し続ける現象であり、ゴミ・Garbageですね。

だとしたら、このゴミ問題を解決するために、PHPではどういう機能を提供しているのでしょうか。

その一つが、次回に登場するGC・Garbage Collectionになります。
そこは、次回に詳しく説明することになります。

3. Summary

今回で、最低限に覚えて頂くと良い内容は以下になります。
- Memory Leakとは、「もう使えない変数データが、メモリを専有し続ける現象」であり、「ゴミ」が残る現象
- 循環参照は、Memory Leakのわかりやすい例であり、GCが収集する対象としてのわかりやすい一例
- このゴミ問題を解決するために、PHPではどういう機能の一つが、GC・Garbage Collection

後書き

1話から今回まではGCの背景になる、変数の仕様・動作・消滅メカニズムなどを話してきました。

次からは、本格的にGCのメカニズムに対して話していきます。

GCの発生条件や明示的に発生させる方法、GCが起きたら行われるメカニズムを解説していきます。この時に、zvalと参照カウントの理解と、変数の消滅基準、消滅せず残り続けるデータの理解が必要なので、それを意識した上でご覧頂けると嬉しいです。

説明とは不足なところか、分かりづらいところはあるかもですが、フードバック頂けると補足とか訂正いたしますので、宜しくお願いします。

※注釈

※1
▶ resourceタイプの解除を忘れること

実のところ最近は、resourceタイプに対しても使われなくなったら自動解除してくれたりします。しかし例外な場合も無いわけでは無いのでresourceタイプ(または違う言語での類似タイプ)の解除は意識しておくと良いです。!

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

bindValue()の仮引数は何を示しているのか?

この記事でわかること

  • PHPのPDOクラスの関数bindValue()の仮引数の中身がわかる

そもそもbindValue()とは

public PDOStatement::bindValue ( mixed $parameter , mixed $value [, int $data_type = PDO::PARAM_STR ] ) : bool

プリペアドステートメントで使用する SQL 文の中で、 対応する名前あるいは疑問符のプレースホルダに値をバインドします。
 PHP公式リファレンス

ものすごく雑に要約すると、bindValue()とは、
下記SQLクエリ文の"?"部分を別の文字列に置換することができる関数です。
SQLインジェクション対策ですね。

$sql = "insert into phptodo (name, done, priority) values (?, 0, ?)";

特に、プリペアドステートメント?PDO?な方は、こちらの記事にまとまっているので参照すると幸せになれます。

SQLインジェクション?という方は、安全なWebサイトの作り方を参照すると幸せになれます。

なお、bindValue()は第三引数まで取ります。

bindValue()の各引数について考える

サンプルコード

     $name = "hoge"
     $priority = "high"

     $sql = "insert into phptodo (name, done, priority) values (?, 0, ?)";  // SQLインジェクション対策のプレースホルダ(=?)
     $stmt = $dbh->prepare($sql);

     $stmt->bindValue(1, $name, PDO::PARAM_STR);

     $stmt->bindValue(2, $priority, PDO::PARAM_STR);
     $stmt->execute();

// $sql = "insert into phptodo (name, done, priority) values ("hoge", 0, "high")"
// となる


bindValue()の第一引数

どのプレースホルダ( $sql 内の"?"のこと ) に関数を適用するかを数字で指定します。

例えばサンプルコードでは values(?, 0, ?)となっているので、

values内の第一引数の "?" に変更を加えたい場合 => 1
values内の第三引数の "?" に変更を加えたい場合 => 2

と記述します。
数字は0ではなく1から始まるので注意!

bindValue()の第二引数

第一引数で指定したプレースホルダに何を代入するかを指定します。
ここでは変数 $name$priority を代入しています。

bindValue()の第三引数

第二引数で指定した内容をどのようなデータ型にするかを指定します。

PDO::PARAM_INT PDO::PARAM_STRなど

これらはInteger型の定数で、例えばPDO::PARAM_STRはInteger型の2を表しています。
(このため、PDO::PARAM_STRではなく2と記述しても同等の内容が実行されます)

print(PDO:PARAM_STR)

// 2

他の定数はここ

プレースホルダに"?"を使わない方法

蛇足ですが、プレースホルダを任意の文字列にして可読性を高める方式もあります。

SQL文の"?"を:hoge:fugaとして、
更にbindValue()の第一引数を数字ではなく":hoge"":fuga"と指定します。

サンプルコード

    $sql = "insert into phptodo (name, done, priority) values (:name, 0, :priority)";  
    $stmt = $dbh->prepare($sql);

    $stmt->bindValue(":name", $name, PDO::PARAM_STR);
    $stmt->bindValue(":priority", $priority, PDO::PARAM_STR);
    $stmt->execute();

参考

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

メモ書き : Laravel8 Release & Upgrade

2020/09/08 リリース。バグフィックス期間は 2021/03/08 セキュリティフィックスは2021/09/08 までという予定。

そして、新機能が多いため Upgrade の量は少ないですが、factory をゴリゴリ使っているとちょっと注意です。( migration の schema:dump やった後の migrate したときの挙動が怖そうなので、そこらへんは自己責任でしてください)

アップグレードに対する更新をソースコード単位で見たい場合は Github comparision tool を見ると良いですよ。

個人的なメモ: upgrade の時は phpstan で静的解析を通すのが良いですよーよー。

[新機能] Laravel Jetstream

see : https://github.com/laravel/jetstream

新しいパッケージだそうです。

Scaffolding の livewireInertiaを使って、ええ感じに Vue や Tailwind CSS っぽく使って…?

なんやこれ全くわからん。bladeを抜かした管理画面を自動で作るよってことかしら。

[新機能] Model Directory

Eloquent Model の初期位置が app/Models の中になりました。

[新機能] Model Factory Classes

factory がクラスになりました。例えば User factory が旧だとベタなPHPスクリプトだったのが、クラスで指定するようになります。

旧 Factory を使う場合は laravel/legacy-factories を使うことになります。極力書き換えるのをおすすめします。

<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */

use Faker\Generator as Faker;

$factory->define( \App\Eloquent\User::class, function (Faker $faker){
    return [
            'name' => $this->faker->name,
            'email' => $this->faker->unique()->safeEmail,
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
    ];
} );

新::

<?php

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class UserFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = User::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'name' => $this->faker->name,
            'email' => $this->faker->unique()->safeEmail,
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }
}

これに伴い、Eloquent 側からfactory を作ることになるそうです。

<?php

use App\Model\User;

User::factory()->count(50)->create();

[新機能] Migration Squash

SQL で書かれたスキーマの出力ができるようになります。出力されたスキーマは database/schema に保存されます。

migration にも関わってくる。(後で)

php artisan schema:dump

[新機能] Job Batching

Bus でバッチ処理が追加されて、axios みたいな書き方で使えるっぽいです。

<?php
use App\Jobs\ProcessPodcast;
use App\Podcast;
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Batch;
use Throwable;

$batch = Bus::batch([
    new ProcessPodcast(Podcast::find(1)),
    new ProcessPodcast(Podcast::find(2)),
    new ProcessPodcast(Podcast::find(3)),
    new ProcessPodcast(Podcast::find(4)),
    new ProcessPodcast(Podcast::find(5)),
])->then(function (Batch $batch) {
    // All jobs completed successfully...
})->catch(function (Batch $batch, Throwable $e) {
    // First batch job failure detected...
})->finally(function (Batch $batch) {
    // The batch has finished executing...
})->dispatch();

return $batch->id;

[新機能] Improved Rate Limit

Requtest Rate Limit を throttle ミドルウェアで制御してるが、もうちょっとフレキシブルにレートリミットを変えられるようになるっぽいです。

例えば、会員ユーザのレートリミットは無し、それ以外はレートリミットは100回/分にする、とする場合は下記のように変更できるみたいです。

<?php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;


// おそらく AppServiceProvider の中で設定
RateLimiter::for('uploads', function (Request $request) {
    return $request->user()->vipCustomer()
                ? Limit::none()
                : Limit::perMinute(100);
});

ミドルウェアの throttole で指定することで使える様になるみたいです。

Route::middleware(['throttle:uploads'])->group(function(){
  Route::post('/audio', function(){ });

  Route::post('/video', function(){ });
});

[新機能] Improved Maintenance Mode

php artisan down でメンテナンスモードにした時の挙動を云々。

  1. --secret='1630542a-246b-4b66-afa1-dd72a4c43515'

このオプションを使うことで / に来たユーザは /1630542a-246b-4b66-afa1-dd72a4c43515 にリダイレクトされます。

  1. --render='errors::503'

このオプションを使うことで、表示するテンプレを指定することができる。

[新機能] Closure Dispatch/Chain

dispatch helper に ->catch(function(){}) ができるようになりました。おそらく、上記の Bus と同じ理屈。

<?php

use Throwable;

dispatch(function () use ($podcast) {
    $podcast->publish();
})->catch(function (Throwable $e) {
    // This job has failed...
});

[新機能] Dynamic Blade Components

Blade::component('package-alert', AlertComponent::class); で登録したコンポーネントを <x-package-alert/> とテンプレート側に書くことで使えるようになります。

クラス名は camelCase で書き、コンポーネント名は kebab-case で書くみたい。

Component Methods

コンポーネントテンプレート内でメソッドを動かすことができる。

/**
 * Determine if the given option is the current selected option.
 *
 * @param  string  $option
 * @return bool
 */
public function isSelected($option)
{
    return $option === $this->selected;
}
<option {{ $isSelected($value) ? 'selected="selected"' : '' }} value="{{ $value }}">
    {{ $label }}
</option

(何か vue みたいだなとぼんやり)

[新機能] Event Listener Improvements

Facade で \Event::listen をする時に、Queueを使うようにデコレートする事ができます。

use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;

Event::listen(queueable(function (PodcastProcessed $event) {
    //
}));

そして queue job のように細かくしてする事ができて ->catch チェインができます。

Event::listen(queueable(function (PodcastProcessed $event) {
    //
})->catch(function (PodcastProcessed $event, Throwable $e) {
    // The queued listener failed...
})->onConnection('redis')
  ->onQueue('podcasts')
  ->delay(now()->addSeconds(10))
);

[新機能] Time Testing Helpers

Carbon::now() で返す時間を指定することができます。

[新機能] Artisan serve Improvements

.env ファイルを更新したばあいに、artisan serve は自動に設定を読み直します。

[新機能] Tailwind Pagination Views

Pagination のテンプレートに今までの Bootstrap3, Bootstrap4 の他に Tailwind が使えます。

[Upgrade] パッケージ依存

  • "laravel/framework": "^8.0"
  • "nunomaduro/collision": "^5.0"
  • "guzzlehttp/guzzle": "^7.0.1"
  • "facade/ignition": "^2.3.6"

他のパッケージを使っているばあいは、それらもアップデートしてください

[Upgrade][Medium] Use PHP >=7.3.0

事情により PHP は 7.3.0 以上を指定してください。

[Upgrade][Low] isset method

Illuminate\Support\CollectionoffsetExists の挙動がかわりました。ので isset で確認した時の挙動が変わります。

<?php

$collection = collect([null]);

// Laravel 7.x - true
isset($collection[0]);

// Laravel 8.x - false
isset($collection[0]);

[Upgrade][High] Model Factories

model factories がクラス化されました。7.x までの factory を書き換えをせずに使い続ける場合は laravel/legacy-factory を使う必要があります。

[Upgrade][Low] The Castable Interface

CastablecastUsing の引数 $arguments に、arrayのタイプ指定が加わりましたので、追加をしてください。

[Upgrade][Low] Increment / Decrement Events

model の update および save が動いた時に increment,decrement インスタンスメソッドが動きます。

[Upgrade][Low] The Dispatcher Contract

Illuminate\Contracts\Events\Dispatcherlisten メソッドの第二引数 $listener の標準値に null を付けられました。

public function listen($events, $listener = null);

[Upgrade][Optional] Mentenance Mode Updates

新しく入った機能 --render 等を使う場合は public/index.php を更新してください。この条件分岐の行は LARAVEL_START の直下に書く必要があります。

<?php
define('LARAVEL_START', microtime(true));

if (file_exists(__DIR__.'/../storage/framework/maintenance.php')) {
    require __DIR__.'/../storage/framework/maintenance.php';
}

[Upgrade][Low] Manager $app Property

Illuminate\Support\Manager クラスの $app プロパティが消えました。もしそのプロパティを使用している場合は $container プロパティを使用してください。

[Upgrade][Low] The elixir Helper

elixir ヘルパーは削除されました。Laravel Mix に移行してください。

[Upgrade][Low] Mail::sendNow Method

Mail facade にあった sendNow メソッドは削除されました。 send メソッドを使ってください。

[Upgrade][High] Pagination Defaults

Paginate の標準テンプレートが Tailwind CSS になりました。いままでの BootStrap を使い続ける場合は AppServiceProviderboot メソッドに \Illuminate\Pagination\Paginator::useBootstrap(); を記入してください。

[Upgrade][High] Queue::retryAfter method / property

QueuedJob, Mailer, Notification, Listener にある retryAfter メソッドと retryAfter プロパティの名前が backoff に変更されました。

[Upgrade][High] Queue::timeoutAt property

QueueJob、Notification, Listener の timeoutAt プロパティが retryUntil になりました。

[Upgrade][Optional] Failed Jobs Table Batch Support

queue_driver がdatabasefailed_jobs を使用している時に、uuid フィールドが必要になります。

<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('failed_jobs', function (Blueprint $table) {
    $table->string('uuid')->after('id')->unique();
});

次に queue.phpfailed.driverdatabase-uuids を設置します。

[Upgrade][Low] The cron-expression Library

Cron 書式の解析に使用している dragonmantank/cron-expression のバージョンが 3.x に更新されました。
下手に込み入った書き方をしている場合だと意図しない時間に動きますので、変更履歴を見て確認をする必要があります。

[Upgrade][Low] The Session Contract

\Session::pull() メソッドが追加されました。

/**
  * Get the value of a given key and then forget it.
  *
  * @param  string  $key
  * @param  mixed  $default
  * @return mixed
  */
 public function pull($key, $default = null);

[Upgrade][Medium] Testing, The assertExactJson Method

assertExactJson メソッドは数値キーの順番が同じである事をチェックするようになりました。順番が違っても良い場合は assertSimilarJson を使うようにしてください。

ぼんやり:: こういう事?

$asset = [ 0 =>1,  1 =>1,  2 =>1];
$data  = ['1'=>1, '0'=>1, '2'=>1];
$this->assertExactJson(json_encode($asset), json_encode($data)) // => false
$this->assertSimilarJson(json_encode($asset), json_encode($data)) // => true

[Upgrade][Low] Database Rule Connections

unique,exists ルールに getConnectionName メソッドが追加されました。

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

【Laravel 】localhost/プロジェクト名/public ←publicをなくす方法

調べたところ方法はいくつかあるが一番スマートな方法かなと思ったのでメモとして残します。

.htaccessを作成する

.htaccessとは隠しファイルの1つでリダイレクトなどの設定を記述するファイルのことです。

そこに以下記述をコピペします。

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    RewriteCond %{REQUEST_FILENAME} -d [OR]
    RewriteCond %{REQUEST_FILENAME} -f
    RewriteRule ^ ^$1 [N]

    RewriteCond %{REQUEST_URI} (\.\w+$) [NC]
    RewriteRule ^(.*)$ public/$1 

    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ server.php

</IfModule>

参照:https://gist.github.com/liaotzukai/8e61a3f6dd82c267e05270b505eb6d5a

vscodeなどでコードを貼り付けてLaravelのルートディレクトリに保存します。

保存しても隠しファイルなのでフォルダには表示されていません。
隠しファイルを表示させたい時はそのフォルダで
(macの場合)

command + shift + .(ドット)

を入力すると表示させることができます。

以上で完了です!
localhost/プロジェクト名 でルートディレクトリへ遷移できます。

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

WordPress 決済プラグイン導入

WordPressでクラウドファンディングサービスを構築し、決済プラグインを導入しました。
その試行錯誤です。

「物語を共有するクラウドファンディング」mamenoki

まず、WP Attractive Donations Systemを導入(購入)しました。

カッコイイのですが、
https://codecanyon.net/item/wp-attractive-donations-system-easy-stripe-paypal-donations/16982796
ドルなら良いかもしれないけど、これを1円単位で合わせるのは、現実的ではありません。
それに、違うプラグインですが、
https://liapoc.com/stripe-100.html
と同じ現象が起きました。
Stripeに送信すると、¥1 ⇒ ¥100になる、ドル使用が前提だから?
/wp-content/plugins/WP_AttractiveDonationsSystem/includes/ads-core.php
$amount *= 100;$amount *= 1;で解決しましたが、

  • Steps of the progress bar :の下限額を99円以下にすると機能しない
  • ドル単位にすると、逆に1/100になる

これは厳しい…
(Stripeのアカウントを作って、会社の口座をリンクして、テストまで出来た、のみが成果)

なので、WP Full Stripe (Premium)を導入(購入)しました。

https://paymentsplugin.com/
これは完全に年単位のサブスクなので、注意しないと、永遠に使用料を引き落とされます(たぶん)。

https://yuyauver98.me/wpfullstripe-translation/
を参考に日本語化も行いました。

物語を共有するクラウドファンディング

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

HTML-PHP-post

同一ページ内で2つ以上の送信ボタンの区別

自身の振り返りようです。
<!-- html -->
<form method="post">
    <label>苗字:<input type="text" size="10" name="fname"></label>
    <input type="submit" value="送信">
</form>

<form method="post">
    <label>名前:<input type="text" size="10" name="name"></label>
    <input type="submit" value="送信">
</form>
<?php
if($_SERVER['REQUEST_METHOD'] === 'POST'){ 
        if(isset($_POST['fname']) === TRUE){
            $btn=($_POST['fname']);
        }
        if(isset($_POST['name']) === TRUE){
            $btn=($_POST['name']);
        }
}
?>

何らかの関係で送信ボタンを分けて送りたい場合、このままだとどちらかのボタンを押しても同時に両方の内容が送信されてしまう。
それぞれ個別で送信できるようにするためには

<!-- html -->
<form method="post">
    <label>苗字:<input type="text" size="10" name="fname"></label>
    <button type="submit" name="btn" value="insert">送信</button>
</form>

<form method="post">
    <label>名前:<input type="text" size="10" name="name"></label>
    <button type="submit" name="btn" value="updata">送信</button>
</form>
<?php
if(isset($_POST['btn']) === TRUE){
            $btn=($_POST['btn']);
        }
        if($btn === 'insert'){
            if(isset($_POST['fname']) === TRUE){
                $fname=($_POST['fname']);
            }
        if($btn === 'updata'){
            if(isset($_POST['name']) === TRUE){
                $name=($_POST['name']);
            }
?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelでFirebase Admin SDK for PHPを利用する(トークン再取得編)

Firebase Admin SDK for PHPでは、自動でトークン取得はできないっぽい

例えば、Javascriptなら以下のようにcurrentUser.getIdToken()で取得できる。
期限切れ前に自動で更新してくれるので便利。

firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
  // Send token to your backend via HTTPS
  // ...
}).catch(function(error) {
  // Handle error
});

Firebase Admin SDK for PHPには似たような機能はなかった。
トークンが切れるたびにいちいちログインし直すのもなんだか嫌。

解決方法

    // ログインしていない場合
    if(!Session::get('token')){
      $signInResult = $this->auth->signInAnonymously();
      Session::put('token', $signInResult->idToken());
      Session::put('refresh_token', $signInResult->refreshToken());
    }

    // トークン認証
    try {
      $auth->verifyIdToken(Session::get('token'));
    } catch (\Exception $e) { // ここはExpiredTokenとかに絞った方が良い
      // 期限切れなら更新トークンを使って再取得
      $refresh_token = Session::get('refresh_token');
      $signInResult = $auth->signInWithRefreshToken($refresh_token);
      Session::put('token', $signInResult->idToken());
    }

流れは以下のような感じ。
1)ログイン時の結果にrefresh_tokenがあるので、セッションか何かに取っておく。
2)トークンが期限切れになったらverifyIdToken()で例外が発生するので、signInWithRefreshToken()で再取得

リフレッシュトークンが変更されるタイミングはそんなにないので、とりあえず使いたい場合は上記で間に合うと思う。

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

[ WordPress ] リンクなしでカテゴリを出力する

WordPressの投稿記事に紐づいたカテゴリを出力するには、通常 <?php the_category(); ?> を使いますが、その場合リンクもつきます。

このリンクをなくしたいと思いました。実装方法は、<?php get_the_category(); ?> を用いて一旦配列に格納して、ループで表示させました。また、複数のカテゴリを表示させる際セパレート(,)をつけて見やすくしましたが、最後のカテゴリが出力された際セパレータがつかないよう条件分岐もさせています。

loop.php
<?php
  $categories = get_the_category();
  $length = count($categories);
  $i = 0;

  foreach($categories as $cat){
    $i++;
    if($length == $i){
      echo $cat->cat_name;
    }else{
      echo $cat->cat_name . ', ';
    }
  }
?>

implode を使う

上記を投稿したところ、下記のように implode を使えばシンプルに実装できるとコメントを頂戴しました。

technote-space様、ありがとうございました!

loop.php
<?php
 $categories = get_the_category();
 echo implode(', ', array_map(function($cat) { return $cat->cat_name; }, $categories));
?>

参考URL

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

Laravel オリジナルの設定ファイルを作成してコントローラで呼び出す

目的

  • Laravelの設定ファイルへの追記方法と設定ファイルの読み込み方法をまとめる
  • ※本説明でいう設定ファイルとは.envファイルではなく、アプリ名ディレクトリ/config直下にあるファイルを指す

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2GHzクアッドコア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をインストールする

前提条件

  • 実施環境に記載した環境、またはそれに準ずる環境が用意されておりLaravelアプリが作成されていること。

前提情報

読後感

  • アプリ名ディレクトリ/config直下の設定ファイルに独自の設定を追記することができる。
  • アプリ名ディレクトリ/config直下の設定ファイルに記載されている値を読み込み表示することができる。

概要

  1. 設定ファイルの作成と記載
  2. ルーティング情報の追記
  3. コントローラファイルの作成と記載
  4. ビューファイルの作成と記載
  5. 確認

詳細

  1. 設定ファイルの作成と記載

    1. アプリ名ディレクトリで下記コマンドを実行して設定ファイルを作成する。

      $ vi config_test.php
      
    2. 開いたファイルに下記の内容を追記し保存して閉じる。

      アプリ名ディレクトリ/config/config_test.php
      <?php
      
      return [
          # 文字列の設定
          'config_str' => 'これは設定ファイルで設定された文字列です。',
      ]
      
      ?>
      
  2. ルーティング情報の追記

    1. アプリ名ディレクトリで下記コマンドを実行してルーティングファイルを開く。

      $ vi routes/web.php
      
    2. 下記の内容を追記する。

      アプリ名ディレクトリ/routes/web.php
      Route::get('/config_check', 'ConfigCheckController@config_check')->name('config_check');
      
  3. コントローラファイルの作成と記載

    1. アプリ名ディレクトリで下記コマンドを実行してコントローラファイルを作成する。

      $ php artisan make:controller ConfigCheckController
      
    2. アプリ名ディレクトリで下記コマンドを実行してただいま作成したコントローラファイルを開く。

      $ vi app/Http/Controllers/ConfigCheckController.php 
      

      下記の様に記載を行う。

      アプリ名ディレクトリ/app/Http/Controllers/ConfigCheckController.php
      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      // 下記を追記する
      use Illuminate\Support\Facades\Config;
      
      class ConfigCheckController extends Controller
      {
          // 下記を追記する
          public function config_check()
          {
              // アプリ名ディレクトリ/config直下に存在するconfig_test.phpファイルで定義されているconfig_strの文字列を$strに格納する処理
              $str = Config::get('config_test.config_str');
              return view('checks.config_check', [
                  'str' => $str,
              ]);
          }
          // 上記までを追記する
      }
      
  4. ビューファイルの作成と記載

    1. アプリ名ディレクトリで下記コマンドを実行してビューファイルを格納するディレクトリを作成する。

      $ mkdir resources/views/checks
      
    2. アプリ名ディレクトリで下記コマンドを実行してビューファイルを作成する。

      $ vi resources/views/checks/config_check.blade.php
      
    3. 開いたビューファイルに下記の内容を記載する。

      アプリ名ディレクトリ/resources/views/checks/config_check.blade.php
      {{ $str }}
      
  5. 確認

    1. アプリ名ディレクトリで下記コマンドを実行してローカルサーバを起動する。

      $ php artisan serve
      
    2. ブラウザで下記にアクセスする。(Authの認証機能が付与されているLaravelアプリで下記にアクセスする場合、ログインが必要になる場合がある。)

    3. 下記の様に、設定ファイルに記載した内容がブラウザ上で表示されていれば作業完了である。

      *画像

参考文献

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

数値バリデートがめんどくさい

$id = intval($_REQUEST['id'] ?? 0);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[PHP]365日+曜日を出力する

今日の日付を出力するには下記のように記述する。

day.php
<?php
print(date('n/j(D)'));
?>

nは月を取得する
jは日付けを取得する
Dは曜日を英語の曜日(頭3文字)を取得する

timezoneの設定をしていなければ、直接記述することで変更できる。

day.php
<?php
date_default_timezone_set('Asia/Tokyo');
print(date('n/j(D)'));
?>

ここで、1日後の日付+曜日を取得するにはどうすれば良いか考える。

day.php
<?php
print(date('n/j(D)', strtotime('+1day'));
?>

これで1日後の日付+曜日を取得できた。

strtotimeとはString to Timestampの略で
文字列をTimestamp型に変換できるファンクションのこと。

+1dayなら1日後
-1dayなら1日前
+365dayなら1年後

とすることができる。

strtotime('+1day')

という部分をfor文を用いて365回繰り返せば良さそう。

day.php
<?php
for($i=1; $i<=365; $i++); {
  $date = strtotime('+' . $i . 'day');
  print(date('n/j(D)', $date));
  print "\n";
}
?>

これで365日分取得できました。


[ 解説 ]

①変数i=1として、365以下で繰り返し、毎回変数iに1を足す

for ($i=1; $i<=365; $i++);

$dateという変数にstrtotimeの中身を分解して代入

$date = strtotime('+' . $i . 'day');
//  strtotime('+1day')の、'+1day'の部分 1を$iとしている

strtotimeファンクションを記述していた部分に変数$dateを置く

print(date('n/j(D)', $date));

④見やすいように改行する

print "\n";

ちなみに、{}の部分は下記のように書き換えることもできる

day.php
<?php
for ($i=1; $i<=365; $i++):
  $date = strtotime('+' . $i . 'day');
  print(date('n/j(D)', $date));
  print "\n";
endfor;
?>

こちらの方が何に対する閉じタグなのかがわかりやすい。
while文も同じで下記のように記述できる。

while (....):
  ....
endwhile;

以上です。お疲れ様でした。

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

phpで365日+曜日を出力する

今日の日付を出力するには下記のように記述する。

day.php
<?php
print(date('n/j(D)'));
?>

nは月を取得する
jは日付けを取得する
Dは曜日を英語の曜日(頭3文字)を取得する

timezoneの設定をしていなければ、直接記述することで変更できる。

day.php
<?php
date_default_timezone_set('Asia/Tokyo');
print(date('n/j(D)'));
?>

ここで、1日後の日付+曜日を取得するにはどうすれば良いか考える。

day.php
<?php
print(date('n/j(D)', strtotime('+1day'));
?>

これで1日後の日付+曜日を取得できた。

strtotimeとはString to Timestampの略で
文字列をTimestamp型に変換できるファンクションのこと。

+1dayなら1日後
-1dayなら1日前
+365dayなら1年後

とすることができる。

strtotime('+1day')

という部分をfor文を用いて365回繰り返せば良さそう。

day.php
<?php
for($i=1; $i<=365; $i++); {
  $date = strtotime('+' . $i . 'day');
  print(date('n/j(D)', $date));
  print "\n";
}
?>

これで365日分取得できました。


[ 解説 ]

①変数i=1として、365以下で繰り返し、毎回変数iに1を足す

for ($i=1; $i<=365; $i++);

$dateという変数にstrtotimeの中身を分解して代入

$date = strtotime('+' . $i . 'day');
//  strtotime('+1day')の、'+1day'の部分 1を$iとしている

strtotimeファンクションを記述していた部分に変数$dateを置く

print(date('n/j(D)', $date));

④見やすいように改行する

print "\n";

ちなみに、{}の部分は下記のように書き換えることもできる

day.php
<?php
for ($i=1; $i<=365; $i++):
  $date = strtotime('+' . $i . 'day');
  print(date('n/j(D)', $date));
  print "\n";
endfor;
?>

こちらの方が何に対する閉じタグなのかがわかりやすい。
while文も同じで下記のように記述できる。

while (....):
  ....
endwhile;

以上です。お疲れ様でした。

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

fitbitのapi連携と集中力判定(PHP,Javascript)

記事の概要

作成したポートフォリオの解説です。以下をまとめています。

  • 背景
  • 主な機能
  • 開発手順
  • 工夫点
  • 課題

背景

「ポモドーロ・テクニック」という時間管理術が話題になっており、そのためのアプリなどもあるが、実際に集中しているかどうかを判断するのは単純なポモドーロタイマーだけでは難しい。
今回は心拍数と集中力の関係に着目し、集中力を判定するアプリを作成しました。

スペック

言語
PHP 7.4.2
javascript
DBMS
MySQL 5.7.26
開発環境
MacOS Catalina 10.15.5
MAMP 5.7
ライブラリ
jquery
フレームワーク
Bootstrap 4.2
バージョン管理
Git 2.24.3
本番環境
xserver
ウェアラブル端末
fitbit inspireHR

主な機能

ポモドーロタイマー

・ホーム画面
スタートを押すと25分タイマーが作動します。
タイマーの下には今日と今週の作業記録が表示されます。
スクリーンショット 2020-09-07 22.13.10.png
・25分が終わると
「お疲れ様です。5分休憩しましょう!」が表示されます。
スクリーンショット 2020-09-07 22.14.39.png

データ

・データ画面
「1週間のポモドーロ回数の推移」と「直近の作業の心拍数の推移が表示されます。
スクリーンショット 2020-09-07 23.00.03.png

開発手順

  1. 要件定義
  2. 環境設定
  3. データベース設計
  4. コーディング
  5. xserverデプロイ

1.用件定義

今回作成するアプリに必要な機能
・タイマー機能
・データ記録
・データ表示
・チャート表示
・fitbitのAPIからのデータ取得

php,Javascriptで開発を行う。

2.環境選定

本番環境では知名度の高い「xserver」を使用する。
GitとGitHubは練習として使っていく。

3.データベース設計

カラムはシンプルに
・ID
・start_at(作業開始時間)
・stop_at(作業終了時間)

4.コーディング

コーディングを実施

4.1データベース作成

MAMPのphpMyAdminを使ってデータベースを作成。

4.2ホーム画面

タイマーのカウント表示はJavasciptで動的に実装。
「START」、「STOP」、「RESET」ボタンを押した時の処理を実装。
作業終了時、DBに自動的に登録される。(Ajax)
画面のデザインはBootstrapを用いて時間短縮。

4.3データ画面

グラフはchartistというjsのライブラリで作成。
作業記録はホーム画面同様、MAMPのDBから取得する。

心拍数の記録はfitbitのAPIから取得。
以下サイトを参考にカスタマイズ
https://qiita.com/RINYU_DRVO/items/6607a0aa7ca3294f8e47

MAMPのDBから直近のデータを取り出してAPIに渡すことで
直近の作業時間あたりの心拍数の推移を取得。
(ここが一番苦労した、、、)

取得した心拍数のデータをもとに
平均心拍数と集中力判定を実施。

集中力判定は以下を基準に作成。
1.高集中:平均心拍数が高く、心拍数の分散(以下、分散)が低い
2.高ストレス:平均心拍数が高く、分散が高い
3.安定集中:平均心拍数が低く、分散が低い
4.集中していない:平均心拍数が低く、分散が高い

※閾値については今後調整します。

5.xserverデプロイ

本番環境ではxserverを使用。
xserver上にDBを立て、プログラムもDB接続部分の修正。

工夫点

・世の中にない機能を考え、実装まで持って行った
・自分で使いたいと思うようなアプリにすることができた

今後の課題

一通りの動作ができた時点で完成としました。
主な課題は以下の通りです。

  • チャートの追加(1週間ごとの比較など)
  • 作業内容の記録(どんな作業の時にどんな集中状態なのか評価するため)

参考文献

Fitbit Web APIとPHP7で心拍数を取得しよう

GitHubアカウント

freedog1
https://github.com/freedog1/pomodoro

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