20200912のPHPに関する記事は15件です。

PHP・GCの話-5話)GC登場。GC発生条件とROOT BUFFER

前書き

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

※ 連載目録

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

Sample Code Link on Github

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

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

今回の話

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

  • 1. GCの登場。Memory Leakを解決するための機能
  • 2. PHPでGCが発生する条件
  • 3. root buffer
  • 3. Summary

1. GCの登場。Memory Leakを解決するための機能 ※1

GC(Garbage Collection)とは、一言で「メモリ内のゴミ自動回収仕組み」と言えます。

前回での話では、メモリ内のゴミ問題を解決するための機能の一つがGarbage Collectionだと話ていました。

これからのメインテーマになります。言葉通りに、ゴミを回収してくれるという意味の機能ですね。

GCは、下のGIFの例えのように、とあるタイミングで、もう使えないのに残っているメモリ内の専有データをまとめて収集し解除してくれます。

20200912120600.gif

2. PHPでGCが発生する条件

PHPでGCが発生する条件は以下の2つがあります。

1) root buffer内に、root zvalの数が上限に達した場合

2020-09-12-23-16-04.png

phpにはroot bufferという空間があります。
そこが最大値まで達すると、GCが発生し、ゴミデータの収集を実行することになります。

一時的にガラクタをおいておく倉庫みたいなものですね。ガラクタ倉庫にいらないと思われるものを入れておき、空間がなくなったら、いらないものを整理し、必要なものはまたおいておくのと、全く同じ概念です。

2) 明示的にGCのcycleを呼び出す

phpでは、GCを明示的に起動できる、以下の関数を提供しています。
しかし、本当に必要と思われる時以外は、使うことはおすすめしません。※2

gc_collect_cycles ( void ) : int

3. root buffer

では、root bufferに関して詳しく見てみましょう。

1) root bufferとは

①今は、解除できない変数データのroot zvalが保存される空間

2020-09-12-23-16-43.png

root zvalとは、変数シンボルと直接繋がれているzvalを言います。
上記のオブジェクトの例で、$objと直接つながっているzvalがそうです。
(上記の絵は実際の構造を簡略化したものなので参考までにご覧ください)※3

root bufferは、該当する変数シンボルが解除される時、参照カウントが1以上で、すぐメモリから解除できない変数データと紐付くroot zvalroot bufferに入れてマーキングして保存しておく仕組みになっています。

②GCがチェックする対象を絞り込むための仕組み

20200912104400.gif

root zvalroot bufferに入れる一番の目的は、「GCがチェックする対象を識別」することです。
root bufferの仕組みにより、GCは「何をチェックすればいいのか」を判別ができるようになり、すべてのzvalをチェックする必要はなくなります。

2) root bufferとGC_ROOT_BUFFER_MAX_ENTRIES

root bufferはbufferである以上、その許容量に上限があります。基本10000個で設定されています。
この設定はphp complieオプションで指定可能です。

// https://github.com/source-comment/php7/blob/1c211996565830feab036cc1daf36a3bed5647e8/Zend/zend_gc.c#L76
// file : Zend/zend_gc.c

#define GC_ROOT_BUFFER_MAX_ENTRIES 10001

3) root bufferが完全に貯まるとGCが発動する

20200912112900.gif

root bufferが、上記でみた上限に達すると、GCが発動し、使われないメモリ内のデータを回収します。
本記事では、サンプルコード内で、以下のようなメソッドで再現しています。

Sample Code Link on Github

/**
 * invoke Garbage Collection to make & unset 10000 of self referenced object's zval.
 * it store to Root Buffer because refCount is 1 Altough life time is ended because refered by itself.
 * When Root Buffer is filled to 10000, Garbage Collection will occur if GC_ROOT_BUFFER_MAX_ENTRIES=10000
 */
private function makeGarbageReferenceTo10000CountWhichPHPDefaultRootBufferMax()
{
    $PHP_DEFAULT_GC_ROOT_BUFFER_MAX_ENTRIES = 10000;

    for($i = 0; $i < $PHP_DEFAULT_GC_ROOT_BUFFER_MAX_ENTRIES; $i++) {
        $a = new \stdClass;
        $a->selfRef = $a;
    }
}

実行結果例

root bufferを10000以上までためたことでGCが発動し、メモリの使用量が約79Mから37Mまでに減っています。

Sample Code Link on Github

public function handle()
{   
    ...
    ini_set('memory_limit', '256M');
    Log::debug(null, ['memory_limit' => ini_get('memory_limit')]);
    ...
    $this->doExampleGcBasic();
    ...(doExampleGcBasicメソッド内)
    Log::debug(null, ['event' => 'invoke', 'msg' => 'garbage collection']);
    $this->makeGarbageReferenceTo10000CountWhichPHPDefaultRootBufferMax();
    $this->logMemUsage();
[2020-09-12 19:05:03] local.DEBUG:  {"memory_limit":"256M"} 
...
[2020-09-12 19:05:04] local.DEBUG:  {"Memory Usage(Bytes)":"79,016,328"} 
[2020-09-12 19:05:04] local.DEBUG:  {"event":"invoke","msg":"garbage collection"}
alive: (refcount=1, is_ref=0)=class App\Console\Commands\AliveInScope { private 
[2020-09-12 19:05:04] local.DEBUG:  {"Memory Usage(Bytes)":"37,612,000"} 
[2020-09-12 19:05:04] local.DEBUG:  {"event":"end","msg":"App\\Console\\Commands\\ExampleGc::doExampleGcBasic"}

4) root bufferと、メモリ、データサイズは無関係

root bufferは、あくまでroot zvalの数だけをチェックします。
データのサイズはチェックしないので、root bufferが上限になる前にmemory limitを超えると、プログラムはダウンしてしまうので注意が必要です。もっと詳しく確認したい方は、サンプルコードのmemory_limitの設定を変えてお試しください。

実行結果例:memory_limit = 64M

メモリが足りず、GC発動までも行かずメモリ関連Fatal errorになりました。

public function handle()
{   
    ...
    ini_set('memory_limit', '64M');
    ...
[2020-09-12 19:09:17] local.DEBUG:  {"memory_limit":"64M"} 
...
[2020-09-12 19:09:17] local.DEBUG:  {"event":"new","msg":"V"} 
PHP Fatal error:  Allowed memory size of 67108864 bytes exhausted (tried to allocate 83886112 bytes) in /var/www/html/subdomain/laravel/app/Console/Commands/ExampleGc.php on line 130

4. Summary

今回で、最低限に覚えて頂くと良い内容は以下になります。
- GCは、Memory Leakを解決するための機能の一つ
- PHPには、GCのための、root bufferという倉庫空間があり、今は解除できない便数のroot zvalを格納する
- root bufferに10000のroot zvalが貯まるとGCは発動する。

後書き

今回は、GCの定義を少し振り返ることと、GCの発動条件と、PHPのGCの核心概念であるroot bufferに関して見ました。
実はこの5話までの内容が色々プログラミング知識のためになる内容だと思います。

次の話では、GCが実際にどういう処理でメモリ回収を行うのかに関してお話しますが、PHPの内部的な話であり、プログラマーが制御できる機能ではないので、実戦ではあんまりやくに立たない話かもしれません。

しかし、GCに関してもっと奥深く理解できる機会、コア機能開発に対するアーキテクチャー設計にも役に立つ話になるとも思います。

そして次回の話でGCの解説は終わり、もう一話でGC StatisticsとWeak Reference Typeを簡単に話をした後、完結になります。

※注釈

※1
▶ GCの登場。Memory Leakを解決するための機能

正しくは「緩和する」が正確な表現かもしれません。実はGCは、Memory Leakを完全に解決する解決策ではなく、あくまで減らしてくれる仕組みからです。

※2
▶ 本当に必要と思われる時以外は、使うことはおすすめしません。
その理由としては、0話の「GCは万能の神様ではない」という内容で話していますので、もしよろしければご参考ください。

※3
▶ (上記の絵は実際の構造を簡略化したものなので参考までにご覧ください)
scalar, array, objectのタイプとPHPバージョンの違いとかにより、シンボルとzval構造体、zval value構造体、zval object構造体などなどの構成図は色々変わってきます。少し昔の資料になりますが、もっと詳しく見たい方は以下のリンクを参考にするといいと思います。
https://yokkuns.hatenadiary.org/entry/20090614/1244994082

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

strlen関数とmb_stlen関数について

今日からPHP学び始めたので初歩的な内容ですがまとめたいと思います。

strlen関数とは

これはrubyでいうところのstring#lengthメソッドと同じです。

構文はこうです。

strlen ( string $string ) : int


引数の( string $string )には文字列が入り: intは返り値となってます。

例えばこうです。

strlen('abc') => 3

ただ日本語の場合は違う返り値になります。
例えば、、、

$text = 'あいうえお'

strlen($text) => 15

なぜ。。。

原因としては日本語のマルチバイトはsjis, utf-8だからです。(現在はほとんどがutf-8みたいです。)

マルチバイトとは

1バイトで表現できない(2バイト以上で表現する)データのことみたいです。。。


バイトってなんだ。。

バイトとは

調べたところ、バイトとは、情報量の単位で、8ビットと等しい情報量のことを指すようです。

ビットってなんだ...

ビットとは

 パソコンの内部でデータを扱う最小単位。
文字や数字などのデータは2進数で扱おり2進数の1桁をビットと呼び、これがデータを扱うときの最小単位になります。
2進数とは0か1で数を表すことを指しますね。

これらをまとめると1バイト(8ビット)は8ビットでは00000000から11111111までの256通りの数を扱えることを指します。

つまり日本語の文字はパターンが漢字もあるし256通りで表現できないということになり、1バイト以上になることがわかります。

そんな1バイトで表現できない(=2バイト以上で表現する)文字を「マルチバイト文字」と呼んだりします。


話がだいぶ逸れましたが

$text = 'あいうえお'

strlen($text) => 15

こうなったのはひらがなの文字列は3バイトだからということでしょう。

もし日本語の文字列の正確な数を取得したい場合はマルチバイトに対応しているマルチバイト文字列関数を使いましょう。

$text = 'あいうえお'

mb_strlen($text) => 5


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

PHPで文字化けするCSV処理を文字化けしなくする方法

結論

fw3/streamscomposer require fw3/streamsして、CSVを扱っている処理を次のように囲ってあげれば文字化けしなくなります。

※ PHP7.2.0未満(下限PHP5.3.3)で解決したい場合はfw3_for_old/streamsを利用してください。(composer require fw3_for_old/streams
  fw3/streamsおよびfw3_for_old/streams共にPHP8.0 beta 2まで動作確認済みのため、PHP7.2.0以上のシステムの場合はどちらを入れても変わりはありません。
※ composerを使用できない環境の場合は、こちらからZIPファイルをダウンロードし、展開して任意のディレクトリにコピーしてください。
  その後、使用対象となる処理より前にrequire_once sprintf('%s/src/filters_require_once.php', $path_to_copy_dir);としてsrc/filters_require_once.phpを読み込むようにしてください。

読み込み

既存の実装が次のような形の場合。

php
$fp     = \fopen($csv_file_path, 'r+b');
$data   = [];
while (($row = \fgetcsv($fp, 1024)) !== FALSE) {
    $data[] = $row;
}
\fclose($fp);

次のようにラップしてあげるだけで解決。

php
<?php

// fw3_for_oldを使用している場合は、`use fw3\`を`use fw3_for_old\` として読み替えてください。
use fw3\streams\filters\utilitys\StreamFilterSpec
use fw3\streams\filters\utilitys\specs\StreamFilterConvertEncodingSpec;
use fw3\streams\filters\utilitys\specs\StreamFilterConvertLinefeedSpec;

$data   = StreamFilterSpec::decorateForCsv(function () use ($csv_file_path) {
    // フィルタの設定
    $spec   = StreamFilterSpec::resource($path_to_csv)->read([
        StreamFilterConvertEncodingSpec::toUtf8()->fromSjisWin(), // Shift_JIS(Windows-31J、MS932)として読み込んでUTF-8として出力
    ]);

//// STA 元のコード
//
    $fp     = \fopen($spec->build(), 'r+b'); // ここだけ `$csv_file_path` を `$spec->build()`に書き換える
    $data   = [];
    while (($row = \fgetcsv($fp, 1024)) !== FALSE) {
        $data[] = $row;
    }
    \fclose($fp);
//
//// END 元のコード

    return $data;
});

書き込み

既存の実装が次のような形の場合。

php
$fp = \fopen($csv_file_path, 'w+b');
foreach ($rows as $row) {
    \fputcsv($fp, $row);
}
\fclose($fp);

次のようにラップしてあげるだけで解決。

php
<?php

// fw3_for_oldを使用している場合は、`use fw3\`を`use fw3_for_old\` として読み替えてください。
use fw3\streams\filters\utilitys\StreamFilterSpec
use fw3\streams\filters\utilitys\specs\StreamFilterConvertEncodingSpec;
use fw3\streams\filters\utilitys\specs\StreamFilterConvertLinefeedSpec;

$data   = StreamFilterSpec::decorateForCsv(function () use ($csv_file_path, $rows) {
    // フィルタの設定
    $spec   = StreamFilterSpec::resource($csv_file_path)->write(array(
        StreamFilterConvertEncodingSpec::toSjisWin()->fromUtf8(),   // UTF-8として読み込んでShift_JIS(Windows-31J、MS932)として出力
        StreamFilterConvertLinefeedSpec::toCrLf()->fromAll(),       // いかなる改行コードであってもCRLFとして出力
    ));

//// STA 元のコード
//
    $fp = \fopen($spec->build(), 'w+b');
    foreach ($rows as $row) {
        \fputcsv($fp, $row);
    }
    \fclose($fp);
//
//// END 元のコード
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】リンクの?と&

PHPについて学習内容を備忘録としてまとめます。

リンクの?と&

パスに?を指定

リンク先のパス名を書くときに値を渡したい時があると思います。
例えば、商品番号であるpro_codeをリンク先に値として渡したいときには?の後に指定します。

#pro_list.php
#rec['code']は飛ぶ先の商品コードが入っており、今回は1が入っています。
#商品の詳細情報を確認するボタン

<a href="../product/pro_disp.php?pro_code='.$rec['code'].'">商品コード1</a>

クリックするとpro_disp.phpに飛ぶ

#pro_disp.php

<?php
print '商品コード1:';
print '.pro_code.';
?>

#実行結果
商品コード:1

このようにパスで指定したpro_codeの値をパス先であるpro_disp.phpに渡しています。

パスに&を指定

パス先に複数の値を返したいときには&を使います。
下記の例では先ほどに追加でユーザーIDを値として渡す場合の処理になります。

#pro_list.php
#商品の詳細情報を確認するボタン
#user_idには1が入っています

<a href="../product/pro_disp.php?pro_code='.$rec['code'].'&user_id=$_SESSION['user_id']">商品コード1</a>

クリックするとpro_disp.phpに飛ぶ

#pro_disp.php

<?php
print '商品コード1:';
print '.pro_code.';

print 'ユーザーID:';
print '.user_id.';

?>

#実行結果
商品コード:1
ユーザーID:1

このように商品IDとユーザーIDを2つパス先に渡すことができました。

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

※学習用メモ デザインパターン:Template Method編

はじめに

使用している言語はPHPです。(自分が良く使うので)

Template Methodパターンとはどういうものか

スーパークラスと呼ばれるクラスで処理の大きな枠組みを決め、
サブクラスと呼ばれるクラスで具体的な内容を記述するというデザインパターンのこと。

似たような処理を共通化させたい際に用いられることが多い

Template Methodパターンを用いるメリット/デメリット

  • メリット

    • スーパークラスに主なロジックが記述されているため、バグが生じた際にスーパークラス内に記述されているTemplateMethodを修正すればよい
    • サブクラスの内容が簡潔になる
  • デメリット

    • サブクラスの数が増加するケースが多い
    • 親子関係が密接であるのでスーパークラスのロジックを確認したうえでないと実装することが難しい
    • スーパークラスの処理が大きくなると、サブクラスの自由度が小さくなってしまう

サンプルコード

仕様

父親とその子どもの一日の行動(平日)を文字列表示する。(いい例が思いつかなかった)

以下のクラスを用意します。

  • OneDayDisplayクラス:抽象クラス

    • 一日の行動を文字列表示(Template Method)
    • その他朝・昼・夜・深夜の行動を抽象メソッドで定義しておく
  • 具象クラス

    • OneDayOfFatherクラス:父親の一日の具体的な行動
    • OneDayOfChildクラス:子どもの一日の具体的な行動

では実際に書いてみる

抽象クラス

OneDayDisplay.class.php
abstract class OneDayDisplay
{
    abstract protected function morning();
    abstract protected function noon();
    abstract protected function night();
    abstract protected function midnight();

    // 一日の行動の表示
    public function oneDayToString()
    {
        echo '朝:' . $this->morning() . PHP_EOL;
        echo '昼:' . $this->noon() . PHP_EOL;
        echo '夜:' . $this->night() . PHP_EOL;
        echo '深夜:' . $this->midnight() . PHP_EOL;
    }
}

上記抽象クラスで朝・昼・夜・深夜の行動を記述するための抽象メソッドを定義。
共通なメソッド(Template Method)としてoneDayToString()を用意し、
こちらでそれぞれの行動を出力できるような形とした。

具象クラス

OneDayOfFather.class.php
class OneDayOfFather extends OneDayDisplay
{
    protected function morning()
    {
        return '出社';
    }

    protected function noon()
    {
        return '外食';
    }

    protected function night()
    {
        return '残業';
    }

    protected function midnight()
    {
        return '晩酌';
    }
}

具象クラスで抽象クラス側で定義した抽象メソッドの具体的な内容を定義する。

OneDayOfChild.class.php
class OneDayOfChild extends OneDayDisplay
{
    protected function morning()
    {
        return '登校';
    }

    protected function noon()
    {
        return '給食';
    }

    protected function night()
    {
        return 'ゲーム';
    }

    protected function midnight()
    {
        return '睡眠';
    }
}

こちらの具象クラスでは先ほどとは異なる内容で定義。

呼び出してみる

$oneDayOfFather = new OneDayOfFather();
$oneDayOfFather->oneDayToString();

$oneDayOfChild = new OneDayOfChild();
$oneDayOfChild->oneDayToString();

/* 以下出力
朝:出社
昼:外食
夜:残業
深夜:晩酌
朝:登校
昼:給食
夜:ゲーム
深夜:睡眠
*/

出力がなんか味気ない....

こう感じた場合先ほどメリットで挙げたように、抽象クラス側の共通メソッドに微修正を加えればよい。

OneDayDisplay.class.php
abstract class OneDayDisplay
{
    abstract protected function morning();
    abstract protected function noon();
    abstract protected function night();
    abstract protected function midnight();

    // 一日の行動の表示(少し装飾)
    public function oneDayToString()
    {
        echo '*-------1日の流れ------*' . PHP_EOL;
        echo '朝:' . $this->morning() . PHP_EOL;
        echo '昼:' . $this->noon() . PHP_EOL;
        echo '夜:' . $this->night() . PHP_EOL;
        echo '深夜:' . $this->midnight() . PHP_EOL;
        echo '*----------------------*' . PHP_EOL;
    }
}

出力してみる

$oneDayOfFather = new OneDayOfFather();
$oneDayOfFather->oneDayToString();

$oneDayOfChild = new OneDayOfChild();
$oneDayOfChild->oneDayToString();


/* 以下出力
*-------1日の流れ------*
朝:出社
昼:外食
夜:残業
深夜:晩酌
*----------------------*
*-------1日の流れ------*
朝:登校
昼:給食
夜:ゲーム
深夜:睡眠
*----------------------*
*/

まとめ

似た流れを共通化するということにより同じようなクラスを複数作成する必要がなくなり、
同様な処理の修正の際も一点を修正すればよくなるというメリットは大きい。

しかし業務などでの大きなプロジェクトで使用する際にはデメリット部分にも注意しどのような設計で開発を進めるか検討する必要はありそうと感じた。

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

※学習用メモ デザインパターン:Template Method編[PHP]

はじめに

使用している言語はPHPです。(自分が良く使うので)

Template Methodパターンとはどういうものか

スーパークラスと呼ばれるクラスで処理の大きな枠組みを決め、
サブクラスと呼ばれるクラスで具体的な内容を記述するというデザインパターンのこと。

似たような処理を共通化させたい際に用いられることが多い

Template Methodパターンを用いるメリット/デメリット

  • メリット

    • スーパークラスに主なロジックが記述されているため、バグが生じた際にスーパークラス内に記述されているTemplateMethodを修正すればよい
    • サブクラスの内容が簡潔になる
  • デメリット

    • サブクラスの数が増加するケースが多い
    • 親子関係が密接であるのでスーパークラスのロジックを確認したうえでないと実装することが難しい
    • スーパークラスの処理が大きくなると、サブクラスの自由度が小さくなってしまう

サンプルコード

仕様

父親とその子どもの一日の行動(平日)を文字列表示する。(いい例が思いつかなかった)

以下のクラスを用意します。

  • OneDayDisplayクラス:抽象クラス

    • 一日の行動を文字列表示(Template Method)
    • その他朝・昼・夜・深夜の行動を抽象メソッドで定義しておく
  • 具象クラス

    • OneDayOfFatherクラス:父親の一日の具体的な行動
    • OneDayOfChildクラス:子どもの一日の具体的な行動

では実際に書いてみる

抽象クラス

OneDayDisplay.class.php
abstract class OneDayDisplay
{
    abstract protected function morning();
    abstract protected function noon();
    abstract protected function night();
    abstract protected function midnight();

    // 一日の行動の表示
    public function oneDayToString()
    {
        echo '朝:' . $this->morning() . PHP_EOL;
        echo '昼:' . $this->noon() . PHP_EOL;
        echo '夜:' . $this->night() . PHP_EOL;
        echo '深夜:' . $this->midnight() . PHP_EOL;
    }
}

上記抽象クラスで朝・昼・夜・深夜の行動を記述するための抽象メソッドを定義。
共通なメソッド(Template Method)としてoneDayToString()を用意し、
こちらでそれぞれの行動を出力できるような形とした。

具象クラス

OneDayOfFather.class.php
class OneDayOfFather extends OneDayDisplay
{
    protected function morning()
    {
        return '出社';
    }

    protected function noon()
    {
        return '外食';
    }

    protected function night()
    {
        return '残業';
    }

    protected function midnight()
    {
        return '晩酌';
    }
}

具象クラスで抽象クラス側で定義した抽象メソッドの具体的な内容を定義する。

OneDayOfChild.class.php
class OneDayOfChild extends OneDayDisplay
{
    protected function morning()
    {
        return '登校';
    }

    protected function noon()
    {
        return '給食';
    }

    protected function night()
    {
        return 'ゲーム';
    }

    protected function midnight()
    {
        return '睡眠';
    }
}

こちらの具象クラスでは先ほどとは異なる内容で定義。

呼び出してみる

$oneDayOfFather = new OneDayOfFather();
$oneDayOfFather->oneDayToString();

$oneDayOfChild = new OneDayOfChild();
$oneDayOfChild->oneDayToString();

/* 以下出力
朝:出社
昼:外食
夜:残業
深夜:晩酌
朝:登校
昼:給食
夜:ゲーム
深夜:睡眠
*/

出力がなんか味気ない....

こう感じた場合先ほどメリットで挙げたように、抽象クラス側の共通メソッドに微修正を加えればよい。

OneDayDisplay.class.php
abstract class OneDayDisplay
{
    abstract protected function morning();
    abstract protected function noon();
    abstract protected function night();
    abstract protected function midnight();

    // 一日の行動の表示(少し装飾)
    public function oneDayToString()
    {
        echo '*-------1日の流れ------*' . PHP_EOL;
        echo '朝:' . $this->morning() . PHP_EOL;
        echo '昼:' . $this->noon() . PHP_EOL;
        echo '夜:' . $this->night() . PHP_EOL;
        echo '深夜:' . $this->midnight() . PHP_EOL;
        echo '*----------------------*' . PHP_EOL;
    }
}

出力してみる

$oneDayOfFather = new OneDayOfFather();
$oneDayOfFather->oneDayToString();

$oneDayOfChild = new OneDayOfChild();
$oneDayOfChild->oneDayToString();


/* 以下出力
*-------1日の流れ------*
朝:出社
昼:外食
夜:残業
深夜:晩酌
*----------------------*
*-------1日の流れ------*
朝:登校
昼:給食
夜:ゲーム
深夜:睡眠
*----------------------*
*/

まとめ

似た流れを共通化するということにより同じようなクラスを複数作成する必要がなくなり、
同様な処理の修正の際も一点を修正すればよくなるというメリットは大きい。

しかし業務などでの大きなプロジェクトで使用する際にはデメリット部分にも注意しどのような設計で開発を進めるか検討する必要はありそうと感じた。

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

Laravel8をインストールしたらやっていること一覧

まえがき

・ヮ・)あ、おはようございまーす

毎回「この前、最初にどんな設定をしたっけ」と悩んでしまうので自分がよくやる設定を備忘録を兼ねてまとめました

Laravel6の設定はこちら

composer create-project しないでインストールした場合

composer create-project してLaravelをインストールすると
.env.example をコピーして .env を作って、APP_KEY の設定もしてくれます

しかし、Laravelのプロジェクトをクローンして composer install でインストールした場合は、手動でやる必要があります

# .env.exampleをコピーして.envを作成
php -r "copy('.env.example', '.env');"
# APP_KEYの生成
php artisan key:generate

APP_NAMEの変更

.env
APP_NAME=アプリケーション名
config/app.php
'name' => env('APP_NAME', 'アプリケーション名'),

public/storageにstorage/app/publicへのシンボリックリンク作成

php artisan storage:link

データベースの設定

.env
DB_CONNECTION=mysql
DB_HOST=ホスト名
DB_PORT=3306
DB_DATABASE=データベース名
DB_USERNAME=ユーザー名
DB_PASSWORD=パスワード

タイムゾーン

config/app.php
-'timezone' => 'UTC',
+'timezone' => 'Asia/Tokyo',

言語設定

config/app.php
-'locale' => 'en',
+'locale' => 'ja',

ダミーデータの日本語化もついでに

config/app.php
-'faker_locale' => 'en_US',
+'faker_locale' => 'ja_JP',

メッセージの日本語化

resources/lang/ja/
に各種メッセージファイルを作る

php -r "copy('https://readouble.com/laravel/8.x/ja/install-ja-lang-files.php', 'install-ja-lang-files.php');"
php -f install-ja-lang-files.php
php -r "unlink('install-ja-lang-files.php');"

よく使うライブラリ導入(お好みで)

# マイグレーションでカラムの定義を変更するのに必要なdbal
composer require doctrine/dbal

# FacadeやModelのPHPDocを生成してIDEでコード補完できるようにしてくれるlaravel-ide-helper
composer require --dev barryvdh/laravel-ide-helper

# デバッグバーを表示してくれるlaravel-debugbar
composer require --dev barryvdh/laravel-debugbar
# laravel-debugbarの設定ファイルをconfig/debugbar.phpに持ってくる
php artisan vendor:publish --provider="Barryvdh\Debugbar\ServiceProvider"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「php artisan ui vue --auth」ができなかった時の解決法【メモ】【超初心者】

$ php artisan ui vue --auth

上記を実行しようとしたところ、、、

エラー内容

「Command "ui" is not defined.」
スクリーンショット 2020-09-12 16.36.16.png

解決した方法

$ composer require laravel/ui "^1.0" --dev

このコマンドを実行してから
またさっきのをやってみたらできました

$ php artisan ui vue --auth

参考にしたサイト https://zukkokeblog.com/laravel6-auth

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

PHP の閉じタグは書かないらしい

PHPのみのファイルを書く場合は、閉じタグは書かない方がバグが起きにくいらしいです。
参考→https://www.php.net/manual/ja/language.basic-syntax.phptags.php

htmlに挿入する場合は必要になります。

また、echoの場合も省略する書き方があるようです。

example.php
<?php echo 'text'; ?>
<?= 'text'; ?>
//どちらも同じ動きをする
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPのopenssl関数で動的に自己署名クライアント証明書を発行する

はじめに

今回はPHPのopenssl関数を使ってクライアント証明書を出力する方法をご紹介します。
利用の際は、セキュリティ面についてご自身でも検証、理解の上でご利用ください。

検証バージョン

以下の環境で動作確認しました。
OS: CentOS 7.8
PHP: 5.5.38
openssl: 1.0.2k
Apache: 2.4.6
※Windows 10上にVagrantで構築しました

前提

事前にApache上にプライベート認証局、クライアント証明書をセットアップしておきます。
以下の記事が非常に参考になりました。
https://qiita.com/mitzi2funk/items/602d9c5377f52cb60e54
上記が完了している前提で以下をご覧ください。

PHPのopenssl関数について

opensslコマンドをPHPから利用できるようにラップした関数群です。
openssl関数の中でも今回は以下を利用します。
以下がそのまま処理の流れになっています。

openssl_pkey_new(クライアント証明書用のキーペア作成)

https://www.php.net/manual/ja/function.openssl-pkey-new.php

openssl_csr_new(クライアント証明書用のCRS作成)

https://www.php.net/manual/ja/function.openssl-csr-new.php

openssl_csr_sign(クライアント証明書への署名)

https://www.php.net/manual/ja/function.openssl-csr-sign.php

openssl_pkcs12_export(クライアント証明書をPKCS12形式へ変換)

https://www.php.net/manual/ja/function.openssl-csr-export.php

コード

ApacheやCA認証局の設定関連が成功していれば、以下のプログラムで、Windows OSなどで利用できるクライアント証明書形式(pkcs12 / *.pfx)を取得できます。

cert.php
<?php

// 各種条件
$client_name = "taro_yamada"; // 認証対象の名前など
$secure_domain = "secure.example.com"; // サーバ名など
$config_file = "/etc/pki/tls/openssl-client.cnf"; // クライアント証明書発行用のconfig
$password = "xxxxxxxx"; // クライアント証明書用パスワード(証明書インストール時に使用)
$cacert_path = "/etc/pki/CA/cacert.pem"; // CA証明書のパス
$cakey_path = "/etc/pki/CA/private/cakey.pem"; // CA秘密鍵のパス
$ca_password = "xxxxxxxx"; // CA証明書作成時に設定したパスワード

// CSR作成
$dn = array(
    "countryName" => "JP",
    "stateOrProvinceName" => "Tokyo",
    "localityName" => "Shibuya-ku",
    "organizationName" => "My Company Co., Ltd.",
    "organizationalUnitName" => "System Dept.",
    "commonName" => $client_name,
    "emailAddress" => "test@example.com"
);

// 新しい 秘密鍵(と公開鍵の) キーペアを生成します
$privkey = openssl_pkey_new(array(
    "private_key_bits" => 2048,
    "private_key_type" => OPENSSL_KEYTYPE_RSA,
));

// CSR を生成します
$configargs = array(
    'digest_alg' => 'sha256',
    'config' => $config_file,
);
$csr = openssl_csr_new($dn, $privkey, $configargs);

// 自己署名の証明書を生成します。365日有効です
$cacert_string = file_get_contents($cacert_path);
$cakey_string = file_get_contents($cakey_path);
$privkey_ca = array($cakey_string, $ca_password);
$x509 = openssl_csr_sign($csr, $cacert_string, $privkey_ca, $days=365, $configargs);

// 秘密鍵、CSR と自己署名証明書をあとで使うために保存します。
//openssl_csr_export($csr, $csrout) and var_dump($csrout);
//openssl_x509_export($x509, $certout) and var_dump($certout);
//openssl_pkey_export($privkey, $pkeyout, $password) and var_dump($pkeyout);
// ↑必要に応じてDBなどに保存

// PKCS12形式へエクスポート($cerificate_outに結果が代入される)
$friendly_name = $client_name . "." . $secure_domain;
$args = array(
               //'extracerts' => $CAcert,
               'friendly_name' => $friendly_name,
              );
openssl_pkcs12_export($x509, $cerificate_out, $privkey, $password, $args);

// 起きたエラーを表示します。
$e = openssl_error_string();
if ($e) {
    while ($e !== false) {
        echo $e . "\n";
    }
    exit;
}

// pfxファイルをダウンロード(WEBサーバ上で実行してダウンロードする場合)
//header("Content-Type: application/force-download");
//header("Content-Length: ".strlen($cerificate_out));
//header("Content-Disposition: attachment; filename=\"{$friendly_name}.pfx\"");
//echo $cerificate_out . "\n";

// ファイル出力
file_put_contents("/path/to/{$friendly_name}.pfx", $cerificate_out);

※説明上簡易的な実装にしています
※WEBアプリケーションで上記を管理するのはセキュリティ的に問題がある可能性があるので、あくまで参考までとしていただければと思います(証明書関連ファイルのパーミッションなど)

メモ1

以下については、ドキュメントなどではopenssl_csr_signの第2引数(証明書の指定)を「file://etc/pki/CA/cacert.pem」の形式で直接ファイル名を指定できる様子でしたが、私の環境ではうまく動作しなかったので事前にテキストを読み込んで、そのテキストを与えるようにしました。

$cacert_string = file_get_contents($cacert_path);
$cakey_string = file_get_contents($cakey_path);
$privkey_ca = array($cakey_string, $ca_password);
$x509 = openssl_csr_sign($csr, $cacert_string, $privkey_ca, $days=365, $configargs);

openssl_csr_signの第2引数については、以下のCの実装などを見ると詳細がわかると思います。
https://github.com/php/php-src/blob/bcd100d812b525c982cf75d6c6dabe839f61634a/ext/openssl/openssl.c#L2491
https://github.com/php/php-src/blob/bcd100d812b525c982cf75d6c6dabe839f61634a/ext/openssl/openssl.c#L1240

メモ2

openssl_x509_exportで取得できる証明書データをDBなどに保存しておくことで、クライアント証明書を個別に無効化できるはずです。
(クライアント証明書からrevokeリストを作成→ApacheにSSLCARevocationFileとしてrevokeリストを読み込ませる)

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

var_dump()ってなんだっけ

突然出てくるvar_dump()。
出てくるたびにこれなんだっけ…となっていたのでまとめ。

まず…

演算について

変数($○○と表記される、プログラムで扱うデータを入れておく容器。箱のようなイメージ。)に対して計算したり、内容の加工を行うことを演算という。

演算にも何種類かあって…

$value = 6;
echo $value + 2;

$value = 6;
echo $value / 2;

このような、加算・減算・乗算・除算・連結は二項演算という。

本題のvar_dump()は次に説明する論理演算で使用する。

論理演算

数値が同じか判定したり、どちらが大きいかを比較するような判定を行う演算子。

ex.

$value = 10;
$result = $value == 20;
var_sump($result);

$value = 10;
$result = $value < 20;
var_sump($result);

$value = 10;
$result = $value > 20;
var_sump($result);

$a = "20";
$b = 20;
$result = $a === $b;
var_sump($result);

結論

論理演算の結果を表示する時は
echoではなくvar_dump()を使用する。

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

Laravelで`Target class [〇〇Controller] does not exist.`と言われたときに検討してみたい原因

Target class [〇〇Controller] does not exist.

まだまだPHPどころかプログラミング自体初心者の域を出ていないですが、自分がぶつかったエラーの対処法を記したいと思います。自分は2時間ぐらい解決に費やしてしまったのでそんな自分の二の舞になる人が減ることを願っています笑。

以下のコードの状態の時にエラーにぶつかりました。

web.php
Route::get('/hello','HelloController@index')
HelloController.php
class HelloController extends Controller
{
    public function index()
    {
        $coolString = 'Hello from Controller.';

        return view('subviews/hello', compact('coolString'));
    }
}
hello.blade.php
<h1>{{ $coolString }}</h1>

とりあえずエラー文をそのままググったのですが〇〇Controllersの部分がApp\Http\Controllers\〇〇Controllersになってるのばかりで、また初心者あるあるのスペルミスを何度も確認したのですがありませんでした。

解決した方法

これまた初心者あるあるかとは思うのですがweb.phpに書いているパスが書き足りなかったです。以下に変えました。(でもこの動画の人はHelloControllerしか書いてないんだよなぁと思いつつ...)

web.php
Route::get('/hello','App\Http\Controllers\HelloController@index')

少しでも参考になりましたら幸いです。

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

PHPで日付の範囲を定義して任意の間隔で処理をする

PHPで日付の範囲を定義して1時間毎など任意の間隔で処理をしたい時、例えば2020年1月1日0時0分0秒から2020年1月2日0時0分0秒まで1時間毎にループを回して何か処理をしたい場合の書き方。

結論

<?php

$date_period = new DatePeriod(
  date_create('2020-01-01 00:00:00'),
  new DateInterval('PT1H'),
  date_create('2020-01-02 00:00:00')->add(new DateInterval('PT1H'))
);

foreach($date_period as $date) {
  echo($date->format('Y-m-d H:i:s') . "\n");
}

ここから実際に動かせます
http://sandbox.onlinephpfunctions.com/code/6590f911729eb743b45d73dbfafcc3eafd158581

解説

DatePeriod

DatePeriodクラスを使うと日付の期間を定義できます。
これがTraversableとなっているのでforeachが回せます。

結論のコードを見て分かる通り、引数の1番目に開始日時、3番目に終了日時をDateTimeで指定します。
2番目には間隔を指定しますが、これは後述のDateIntervalクラスで表現します。
終了日時に1時間追加しているのは最後の日時がループに含まれないためです。

DateInterval

DateIntervalクラスを使うと日付(ループ)の間隔を指定できます。
1時間毎はPT1Hと表現します。詳しくはこちらに書いてあります。

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

wordpressでよく使うphpのコード

はじめに

最近wordpressのテーマを作ることが多いのですが、そろそろ頻出するphpのコードを丸暗記したいなと思ってきたのでまとめます。

スタイルシートの取得

ローカルで書いたりするときはcssの相対パスでリンクさせますが、wordpressの場合はphpでcssを呼び出す必要があるようです。

<link rel="stylesheet" href="<?php echo get_stylesheet_uri(); ?>">

urlじゃなくてuri?と思いましたが、urlが絶対パスを示すのに対してuriは相対パスを示すみたいですね。

プラグインの制御とかに必要らしいやつ

<head>
  <?php wp_head(); ?>
</head>

headにはこれを入れとかなきゃいけないみたいです。
詳しいことはまだ難しそうなんで今は何も考えず入れてます。

  <?php wp_footer(); ?>
</body>

これも必要でbodyの閉じタグの上に置くらしいです。

bodyにクラスを付与

bodyごとにクラスを付与してページごとに区別するためのコード。
ページによって差異を持たせる際に必要なのでこれをbodyに組み込むのがお約束なようです。

<body <?php body_class(); ?>>
  <!--色々なコードさん達-->
</body>

ヘッダー、フッダーの取得

wordpressのテーマエディターはヘッダー、メインコンテンツ、フッダーを別々に管理できるのでメインであるindex.phpには直接書かず、phpのコードでヘッダーやフッダーを取得して画面に表示させることができます。

<header>
  <?php get_header(); ?> <!--header取得-->
</header>

これで予め作ってあるheader.phpを取得して表示します。

footerも同様に

<footer>
  <?php get_footer(); ?> <!--footer取得-->
</footer>

記事に関するコード

記事をクリックしたら該当の記事のリンクに飛んだり、カテゴリをクリックしたらそれに関する記事の一覧が表示されたりとサイトを作る上で最もよく使う機能達。

記事のループ

トップページ等で記事の一覧を表示する場合、記事を表示して次にまた記事があれば表示、無ければ表示の終了を意味するコードです。

<?php if(have_posts()):while(have_posts()):the_post(); ?>
<?php endif; ?>

それぞれの記事にクラスを付与

bodyタグにも書きましたが今度は1つ1つの記事を区別するためのコードを書きます。
個別のクラスを付与することで記事ごとの操作が可能になります。
基本的にarticleタグの中に入れ込むことが多いのではないでしょうか。

<article<?php post_class(); ?>>
  <!--記事の中身-->
</article>

それぞれの記事にリンクを付与

それぞれの記事に個別のリンクを持たせます。
aタグで囲ってhrefに指定すればクリックで記事単体のページに移動できますね。

<a href="<?php the_permalink(); ?>">

タイトルの表示

<?php the_title(); ?>

投稿日時の取得

<?php echo get_the_date(); ?>

カテゴリの取得

<?php the_category(','); ?>

TOPページでは概要を出して個別ページで全文を表示

デフォルトの状態だとTOPにたくさんの記事の全文が表示されごちゃごちゃしてしまいます。
そこで必要となるのが、TOPページで記事の概要を表示し記事の個別ページに移ったら全文を表示させる条件式です。

<?php if(is_single()): ?><!--個別ページなら-->
  <?php the_content(); ?><!--全文表示-->
    <?php else: ?><!--それ以外は-->
  <?php the_excerpt(); ?><!--概要表示-->
<?php endif; ?>

function.phpに記述するコード

これまではindex.phpにてhtmlの中にphpのコードを組み込んでいましたが、ここからはfunction.phpに記述するコードです。

・function.phpとは
index.php(header.php、footer.php)がサイトの見た目のためのコードであるのに対して、function.phpはサイトの設定とか裏側系のやつ。

アイキャッチ機能の有効化


デフォルトではアイキャッチ機能を使えないのでこれで有効化。
add_theme_support('post-thumbnails');

記事の概要の文字数の設定

デフォルトだと概要の文字数は110字。これをサイトデザインに合わせて変更したいときは、my_length($length)に対して変更したい文字数を返します。ここでは概要を90字に変更。

function my_length($length){
    return 90;
}
add_filter('excerpt_mblength','my_length');//概要90字

概要の文字数が上限に達したら...を表示

デフォルトだと概要の文字数に達したら[...]と表示されるのですが、ちょっとかっこわるいので括弧を無くして...だけにしてみます。

function my_more($more){
    return '...';
}
add_filter('excerpt_more','my_more');//90字以上は'...'

cssの追加

TOPページと個別ページでcssを分けたいとき等に相対パスを組み込めばcssファイルを別途で使うことができます。

function twpp_enqueue_styles() {
  wp_enqueue_style( 
    'sub-style',
    get_template_directory_uri() '/css/article.css' 
  );
}

Youtubeの動画に自動的にクラスを付与

記事の中にYoutubeの動画を組み込んだとき、画面の大きさを変えたりレスポンシブ対応させたいといったときにifarameタグを検知し自動的にyoutubeというクラスを付け、styleをいじりやすくします。

function iframe_in_div($the_content) {
  if ( is_singular() ) {
    $the_content = preg_replace('/<iframe/i', '<div class="youtube"><iframe', 
    $the_content);
    $the_content = preg_replace('/<\/iframe>/i', '</iframe></div>', $the_content);
}
  return $the_content;
}
add_filter('the_content','iframe_in_div');
@media screen and (max-width:480px) { 
    /* スマホでは動画を横幅いっぱいに表示 */
  .youtube{
    width:100%;
  }
}

参考文献

WordPressレッスンブック 出版社:ソシム 2014年

THE WORDPRESS PRESS
https://thewppress.com/libraries/enqueue-styles/

Office Kondo
https://takayakondo.com/wordpress-youtube-responsive/


おわりに

現時点で思いついたものを挙げてみました。
なぜこの処理をするのかが理解できてないものがいくつかあり要勉強です。
これから随時更新していければと思います。

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

Docker × Laravel 8 Jetstream でログイン、ユーザー登録、2要素認証を実装する

Laravel Jetstream は Laravel8から使える新しいパッケージで、Laravel7以前で利用されていた Laravel UI の後継パッケージとなります。

Laravel Jetstreamの機能

  • ログイン機能
  • ユーザー登録機能
  • メール検証
  • 2要素認証
  • セッション管理
  • Laravel SanctumによるAPIサポート
    • チーム管理

上記の機能を提供します。

Laravel Jetstreamの特徴

JetstreamはTailwind CSSを使用して設計されています。
テンプレートとして、LivewireまたはInertiaを選択できます。

  • Livewireは主にBladeで書く人向け(SEO、OGPが必要)
  • Inertiaは主にVueで書く人向け(SPA)

環境

  • PHP: 7.4.4
  • Laravel: 8.1.0
  • Node: 14.2.0
  • Yarn: 1.22.4
  • Laravel Jetstream: 0.6.0

環境としてこちらの記事、リポジトリを参考にします。

$ git clone git@github.com:ucan-lab/docker-laravel.git
$ cd docker-laravel
$ make create-project

http://127.0.0.1

パスワードリセットメールの動作確認をしたい場合は、下記の記事でメールコンテナを追加すると確認できます。

Livewire(Blade) インストール

$ make app
$ composer require laravel/jetstream
$ php artisan jetstream:install livewire
$ php artisan migrate
$ exit
$ make web
$ yarn install
$ yarn dev
$ exit

http://127.0.0.1

ScreenShot 2020-09-12 4.48.10.png

Welcome画面が表示されればok

補足: Livewire のファイル差分

$ git status -s
 M .env.example
 M app/Http/Kernel.php
 M app/Models/User.php
 M app/Providers/RouteServiceProvider.php
 M composer.json
 M composer.lock
 M config/app.php
 M config/session.php
 M database/migrations/2014_10_12_000000_create_users_table.php
 M package.json
 M resources/css/app.css
 M resources/views/welcome.blade.php
 M routes/api.php
 M routes/web.php
 M webpack.mix.js
?? app/Actions/Fortify/CreateNewUser.php
?? app/Actions/Fortify/PasswordValidationRules.php
?? app/Actions/Fortify/ResetUserPassword.php
?? app/Actions/Fortify/UpdateUserPassword.php
?? app/Actions/Fortify/UpdateUserProfileInformation.php
?? app/Actions/Jetstream/DeleteUser.php
?? app/Providers/FortifyServiceProvider.php
?? app/Providers/JetstreamServiceProvider.php
?? app/View/Components/AppLayout.php
?? app/View/Components/GuestLayout.php
?? config/fortify.php
?? config/jetstream.php
?? config/sanctum.php
?? database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php
?? database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php
?? database/migrations/2020_09_11_191956_create_sessions_table.php
?? public/css/app.css
?? resources/views/api/api-token-manager.blade.php
?? resources/views/api/index.blade.php
?? resources/views/auth/forgot-password.blade.php
?? resources/views/auth/login.blade.php
?? resources/views/auth/register.blade.php
?? resources/views/auth/reset-password.blade.php
?? resources/views/auth/two-factor-challenge.blade.php
?? resources/views/auth/verify-email.blade.php
?? resources/views/dashboard.blade.php
?? resources/views/layouts/app.blade.php
?? resources/views/layouts/guest.blade.php
?? resources/views/profile/delete-user-form.blade.php
?? resources/views/profile/logout-other-browser-sessions-form.blade.php
?? resources/views/profile/show.blade.php
?? resources/views/profile/two-factor-authentication-form.blade.php
?? resources/views/profile/update-password-form.blade.php
?? resources/views/profile/update-profile-information-form.blade.php
?? tailwind.config.js

Inertia(Vue) インストール

$ make app
$ composer require laravel/jetstream
$ php artisan jetstream:install inertia --teams
$ php artisan migrate
$ exit
$ make web
$ yarn install
$ yarn dev
$ exit

http://127.0.0.1

ScreenShot 2020-09-12 4.48.10.png

Welcome画面が表示されればok

補足: Inertia のファイル差分

$ git status -s
 M backend/.env.example
 M backend/app/Http/Kernel.php
 M backend/app/Models/User.php
 M backend/app/Providers/AuthServiceProvider.php
 M backend/app/Providers/RouteServiceProvider.php
 M backend/composer.json
 M backend/composer.lock
 M backend/config/app.php
 M backend/config/session.php
 M backend/database/migrations/2014_10_12_000000_create_users_table.php
 M backend/package.json
 M backend/resources/css/app.css
 M backend/resources/js/app.js
 M backend/resources/views/welcome.blade.php
 M backend/routes/api.php
 M backend/routes/web.php
 M backend/webpack.mix.js
?? backend/app/Actions/Fortify/CreateNewUser.php
?? backend/app/Actions/Fortify/PasswordValidationRules.php
?? backend/app/Actions/Fortify/ResetUserPassword.php
?? backend/app/Actions/Fortify/UpdateUserPassword.php
?? backend/app/Actions/Fortify/UpdateUserProfileInformation.php
?? backend/app/Actions/Jetstream/AddTeamMember.php
?? backend/app/Actions/Jetstream/CreateTeam.php
?? backend/app/Actions/Jetstream/DeleteTeam.php
?? backend/app/Actions/Jetstream/DeleteUser.php
?? backend/app/Actions/Jetstream/UpdateTeamName.php
?? backend/app/Models/Membership.php
?? backend/app/Models/Team.php
?? backend/app/Policies/TeamPolicy.php
?? backend/app/Providers/FortifyServiceProvider.php
?? backend/app/Providers/JetstreamServiceProvider.php
?? backend/app/View/Components/GuestLayout.php
?? backend/config/fortify.php
?? backend/config/jetstream.php
?? backend/config/sanctum.php
?? backend/database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php
?? backend/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php
?? backend/database/migrations/2020_05_21_100000_create_teams_table.php
?? backend/database/migrations/2020_05_21_200000_create_team_user_table.php
?? backend/database/migrations/2020_09_11_221935_create_sessions_table.php
?? backend/public/css/app.css
?? backend/resources/js/Jetstream/ActionMessage.vue
?? backend/resources/js/Jetstream/ActionSection.vue
?? backend/resources/js/Jetstream/ApplicationLogo.vue
?? backend/resources/js/Jetstream/ApplicationMark.vue
?? backend/resources/js/Jetstream/Button.vue
?? backend/resources/js/Jetstream/ConfirmationModal.vue
?? backend/resources/js/Jetstream/DangerButton.vue
?? backend/resources/js/Jetstream/DialogModal.vue
?? backend/resources/js/Jetstream/Dropdown.vue
?? backend/resources/js/Jetstream/DropdownLink.vue
?? backend/resources/js/Jetstream/FormSection.vue
?? backend/resources/js/Jetstream/Input.vue
?? backend/resources/js/Jetstream/InputError.vue
?? backend/resources/js/Jetstream/Label.vue
?? backend/resources/js/Jetstream/Modal.vue
?? backend/resources/js/Jetstream/NavLink.vue
?? backend/resources/js/Jetstream/ResponsiveNavLink.vue
?? backend/resources/js/Jetstream/SecondaryButton.vue
?? backend/resources/js/Jetstream/SectionBorder.vue
?? backend/resources/js/Jetstream/SectionTitle.vue
?? backend/resources/js/Jetstream/SwitchableTeam.vue
?? backend/resources/js/Jetstream/Welcome.vue
?? backend/resources/js/Layouts/AppLayout.vue
?? backend/resources/js/Mixins/InteractsWithErrorBags.js
?? backend/resources/js/Pages/API/ApiTokenManager.vue
?? backend/resources/js/Pages/API/Index.vue
?? backend/resources/js/Pages/Dashboard.vue
?? backend/resources/js/Pages/Profile/DeleteUserForm.vue
?? backend/resources/js/Pages/Profile/LogoutOtherBrowserSessionsForm.vue
?? backend/resources/js/Pages/Profile/Show.vue
?? backend/resources/js/Pages/Profile/TwoFactorAuthenticationForm.vue
?? backend/resources/js/Pages/Profile/UpdatePasswordForm.vue
?? backend/resources/js/Pages/Profile/UpdateProfileInformationForm.vue
?? backend/resources/js/Pages/Teams/Create.vue
?? backend/resources/js/Pages/Teams/CreateTeamForm.vue
?? backend/resources/js/Pages/Teams/DeleteTeamForm.vue
?? backend/resources/js/Pages/Teams/Show.vue
?? backend/resources/js/Pages/Teams/TeamMemberManager.vue
?? backend/resources/js/Pages/Teams/UpdateTeamNameForm.vue
?? backend/resources/views/app.blade.php
?? backend/resources/views/auth/forgot-password.blade.php
?? backend/resources/views/auth/login.blade.php
?? backend/resources/views/auth/register.blade.php
?? backend/resources/views/auth/reset-password.blade.php
?? backend/resources/views/auth/two-factor-challenge.blade.php
?? backend/resources/views/auth/verify-email.blade.php
?? backend/resources/views/layouts/guest.blade.php
?? backend/tailwind.config.js

Screen Shot(Livewire, Inertia 共通)

welcome

Jetstreamをインストールすることで右上に Login, Register のメニューが追加されています。

ScreenShot 2020-09-12 4.48.10.png

register

ユーザー登録画面です。

ScreenShot 2020-09-12 4.48.25.png

dashboard

ユーザー登録するとログインされ、ダッシュボードに遷移します。

ScreenShot 2020-09-12 4.50.17.png

profile

スクリーンショット2020-09-12(04.50.40).png

api tokens

ScreenShot 2020-09-12 4.51.10.png

login

ScreenShot 2020-09-12 4.51.37.png

password reset

ScreenShot 2020-09-12 4.51.43.png

ScreenShot 2020-09-12 4.55.55.png

ScreenShot 2020-09-12 4.56.50.png

Screen Shot(Inertia)

dashboard

ScreenShot 2020-09-12 8.11.47.png

team settings

ScreenShot 2020-09-12 8.12.32.png

create new team

ScreenShot 2020-09-12 8.12.44.png

2要素認証

profile ページから Two Factor Authenticationを有効にするとQRコードが表示されます。
Google Authenticator をインストールし、カメラでQRコード読み込むと2要素認証できます。

ScreenShot 2020-09-12 8.27.51.png

login ページを進むと認証コードを求めるページが表示される。

ScreenShot 2020-09-12 8.29.00.png

ノーコードでここまでできるとは恐れいりますね...

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