- 投稿日:2019-02-28T23:35:44+09:00
殺人事件を Exception で再現してみる feat. ポートピア連続殺人事件
これは何?
- 昔の有名な殺人事件を、Exception の try~catch で再現してみます。
- Exceptionの握りつぶしについて「完全に握りつぶす」「1つ以外を握りつぶす」「一切握りつぶさない」の3パターンを説明します。
- ソースコード(portpia.php)は最後に全部掲載します。
はじまり
事件発生!
山川社長が何者かに殺害された。
刑事のヤスが事件解決の為、捜査を開始した。山川社長、文江、ヤスを呼び出す。
new でオブジェクトを作ります。ここではクラス定義は秘密にしておきます。(最後に掲載します)
$objs = []; $objs[] = new yamakawa(); $objs[] = new fumie(); $objs[] = new yasu();聴取1回目:自己紹介させます。
Exceptionに重要発言を持たせています。ここでは、自己紹介するだけなので、Exceptionを完全に握りつぶします。
foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // 吐かないよう握りつぶす ; } }実行結果
山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい!聴取2回目:事情を聴いてみます。
重要発言(ようはException)を1つだけ聴いてみます。
foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // 1つだけ吐かせる $obj->say($e->getMessage()); } }実行結果
山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! 山川(殺害された人) >>> 実は、昔、詐欺をやっていたという過去があります 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! 文江(山川の秘書) >>> 実は、ヤスは私の兄です ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい! ヤス(捜査している人) >>> 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります聴取3回目:真実を吐かせてみる
全ての重要発言、ようは全てのExceptionを聴いてみます。ネストしたExceptionを全て展開しているだけです。
コード
foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // 全部吐かせる do { $obj->say($e->getMessage()); } while($e = $e->getPrevious()); } }実行結果
山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! 山川(殺害された人) >>> 実は、昔、詐欺をやっていたという過去があります 山川(殺害された人) >>> 実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! 文江(山川の秘書) >>> 実は、ヤスは私の兄です 文江(山川の秘書) >>> 実は・・・兄の犯罪に加担していました ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい! ヤス(捜査している人) >>> 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります ヤス(捜査している人) >>> 実は・・・詐欺の黒幕は山川社長と知り、復讐のため私が殺害しました聴取の総括:どこで吐いたのか
事件は解決したんですが、ソースコード中のどこで重要発言があったのか(つまりExceptionが発生したのか)全部出力してみます。
foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // どこで吐いたのかも含めて、全部吐かせる // print $e."\n"; でいいのだけど、出力が見づらいので行頭を少し加工 print preg_replace('/^/m', ' |', $e)."\n"; } }実行結果
山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! |Exception: 実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました in /home/ubuntu/20190227/portopia.php:31 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(23): yamakawa->deepListening() |#1 /home/ubuntu/20190227/portopia.php(137): yamakawa->listening() |#2 {main} | |Next Exception: 実は、昔、詐欺をやっていたという過去があります in /home/ubuntu/20190227/portopia.php:25 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(137): yamakawa->listening() |#1 {main} : (以降長いので省略、最後に全部載せてます)まとめ
- 犯人はヤス
- 重要発言、つまり Exception は聞き漏らさないようにしましょう。
catch(Exception $e){ ; }
すると、重要発言は何も聞けない。- 重要発言を聞くために
catch(Exception $e){ print $e->getMessage(); }
としても、1つしか聞けない。- 重要発言を聞き漏らさない為には
catch(Exception $e){ print $e; }
とする。添付:全ソースコード
portpia.php<?php abstract class investigation { abstract function whatName(); abstract function listening(); function say($msg) { print $this->whatName() . " >>> " . $msg . "\n"; } } class yamakawa extends investigation { function whatName() { return '山川(殺害された人) '; } function listening() { $this->say("社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました!"); try { $this->deepListening(); } catch (Exception $e) { throw new Exception("実は、昔、詐欺をやっていたという過去があります", 0, $e); } } function deepListening() { throw new Exception("実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました"); } } class fumie extends investigation { function whatName() { return '文江(山川の秘書) '; } function listening() { $this->say("山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです!"); try { $this->deepListening(); } catch (Exception $e) { throw new Exception("実は、ヤスは私の兄です", 0, $e); } } function deepListening() { throw new Exception("実は・・・兄の犯罪に加担していました"); } } class yasu extends investigation { function whatName() { return 'ヤス(捜査している人)'; } function listening() { $this->say("山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい!"); try { $this->deepListening(); } catch (Exception $e) { throw new Exception("実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります", 0, $e); } } function deepListening() { throw new Exception("実は・・・詐欺の黒幕は山川社長と知り、復讐のため私が殺害しました"); } } $objs = []; $objs[] = new yamakawa(); $objs[] = new fumie(); $objs[] = new yasu(); print "\n"; print "***\n"; print "*** 聴取1回目:自己紹介(Exceptionを完全に握りつぶす)\n"; print "***\n"; foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // 吐かないよう握りつぶす ; } } print "\n"; print "***\n"; print "*** 聴取2回目:事情を聴く(直近のExceptionのメッセージ1つだけ吐かせる)\n"; print "***\n"; foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // 1つだけ吐かせる $obj->say($e->getMessage()); } } print "\n"; print "***\n"; print "*** 聴取3回目:真実を吐かせる(ネストしているExceptionのメッセージも含め全部吐かせる)\n"; print "***\n"; foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // 全部吐かせる do { $obj->say($e->getMessage()); } while($e = $e->getPrevious()); } } print "\n"; print "***\n"; print "*** 聴取の総括:どこで吐いたのか(Exceptionのメッセージ以外の情報も全部吐かせる)\n"; print "***\n"; foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // どこで吐いたのかも含めて、全部吐かせる // print $e."\n"; でいいのだけど、出力が見づらいので行頭を少し加工 print preg_replace('/^/m', ' |', $e)."\n"; } }添付:全実行結果
PHP7.2で動作確認しましたが、他のバージョンでも動くと思います。
$ php --version PHP 7.2.15-0ubuntu0.18.04.1 (cli) (built: Feb 8 2019 14:54:22) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.2.15-0ubuntu0.18.04.1, Copyright (c) 1999-2018, by Zend Technologies $ php portopia.php *** *** 聴取1回目:自己紹介(Exceptionを完全に握りつぶす) *** 山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい! *** *** 聴取2回目:事情を聴く(直近のExceptionのメッセージ1つだけ吐かせる) *** 山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! 山川(殺害された人) >>> 実は、昔、詐欺をやっていたという過去があります 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! 文江(山川の秘書) >>> 実は、ヤスは私の兄です ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい! ヤス(捜査している人) >>> 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります *** *** 聴取3回目:真実を吐かせる(ネストしているExceptionのメッセージも含め全部吐かせる) *** 山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! 山川(殺害された人) >>> 実は、昔、詐欺をやっていたという過去があります 山川(殺害された人) >>> 実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! 文江(山川の秘書) >>> 実は、ヤスは私の兄です 文江(山川の秘書) >>> 実は・・・兄の犯罪に加担していました ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい! ヤス(捜査している人) >>> 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります ヤス(捜査している人) >>> 実は・・・詐欺の黒幕は山川社長と知り、復讐のため私が殺害しました *** *** 聴取の総括:どこで吐いたのか(Exceptionのメッセージ以外の情報も全部吐かせる) *** 山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! |Exception: 実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました in /home/ubuntu/20190227/portopia.php:31 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(23): yamakawa->deepListening() |#1 /home/ubuntu/20190227/portopia.php(137): yamakawa->listening() |#2 {main} | |Next Exception: 実は、昔、詐欺をやっていたという過去があります in /home/ubuntu/20190227/portopia.php:25 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(137): yamakawa->listening() |#1 {main} 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! |Exception: 実は・・・兄の犯罪に加担していました in /home/ubuntu/20190227/portopia.php:54 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(46): fumie->deepListening() |#1 /home/ubuntu/20190227/portopia.php(137): fumie->listening() |#2 {main} | |Next Exception: 実は、ヤスは私の兄です in /home/ubuntu/20190227/portopia.php:48 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(137): fumie->listening() |#1 {main} ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい! |Exception: 実は・・・詐欺の黒幕は山川社長と知り、復讐のため私が殺害しました in /home/ubuntu/20190227/portopia.php:77 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(69): yasu->deepListening() |#1 /home/ubuntu/20190227/portopia.php(137): yasu->listening() |#2 {main} | |Next Exception: 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります in /home/ubuntu/20190227/portopia.php:71 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(137): yasu->listening() |#1 {main}
- 投稿日:2019-02-28T23:35:44+09:00
殺人事件の解決までを Exception で再現してみる feat. ポートピア連続殺人事件
これは何?
- 昔の有名な殺人事件を、Exception の try~catch で再現してみます。
- Exceptionの握りつぶしについて「完全に握りつぶす」「1つ以外を握りつぶす」「一切握りつぶさない」の3パターンを使って、徐々に事件の解決に近づきます。
- ソースコード(portpia.php)は最後に全部掲載します。
はじまり
事件発生!
山川社長が何者かに殺害された。
刑事のヤスが事件解決の為、捜査を開始した。山川社長、文江、ヤスを呼び出す。
new でオブジェクトを作ります。ここではクラス定義は秘密にしておきます。(最後に掲載します)
$objs = []; $objs[] = new yamakawa(); $objs[] = new fumie(); $objs[] = new yasu();聴取1回目:自己紹介させます。
Exceptionに重要発言を持たせています。ここでは、自己紹介するだけなので、Exceptionを完全に握りつぶします。
foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // 吐かないよう握りつぶす ; } }実行結果
山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい!聴取2回目:事情を聴いてみます。
重要発言(ようはException)を1つだけ聴いてみます。
foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // 1つだけ吐かせる $obj->say($e->getMessage()); } }実行結果
山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! 山川(殺害された人) >>> 実は、昔、詐欺をやっていたという過去があります 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! 文江(山川の秘書) >>> 実は、ヤスは私の兄です ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい! ヤス(捜査している人) >>> 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります聴取3回目:真実を吐かせてみる
全ての重要発言、ようは全てのExceptionを聴いてみます。ネストしたExceptionを全て展開しているだけです。
コード
foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // 全部吐かせる do { $obj->say($e->getMessage()); } while($e = $e->getPrevious()); } }実行結果
山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! 山川(殺害された人) >>> 実は、昔、詐欺をやっていたという過去があります 山川(殺害された人) >>> 実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! 文江(山川の秘書) >>> 実は、ヤスは私の兄です 文江(山川の秘書) >>> 実は・・・兄の犯罪に加担していました ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい! ヤス(捜査している人) >>> 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります ヤス(捜査している人) >>> 実は・・・詐欺の黒幕は山川社長と知り、復讐のため私が殺害しました聴取の総括:どこで吐いたのか
事件は解決したんですが、ソースコード中のどこで重要発言があったのか(つまりExceptionが発生したのか)全部出力してみます。
foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // どこで吐いたのかも含めて、全部吐かせる // print $e."\n"; でいいのだけど、出力が見づらいので行頭を少し加工 print preg_replace('/^/m', ' |', $e)."\n"; } }実行結果
山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! |Exception: 実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました in /home/ubuntu/20190227/portopia.php:31 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(23): yamakawa->deepListening() |#1 /home/ubuntu/20190227/portopia.php(137): yamakawa->listening() |#2 {main} | |Next Exception: 実は、昔、詐欺をやっていたという過去があります in /home/ubuntu/20190227/portopia.php:25 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(137): yamakawa->listening() |#1 {main} : (以降長いので省略、最後に全部載せてます)まとめ
- 犯人はヤス
- 重要発言、つまり Exception は聞き漏らさないようにしましょう。
catch(Exception $e){ ; }
すると、重要発言は何も聞けない。- 重要発言を聞くために
catch(Exception $e){ print $e->getMessage(); }
としても、1つしか聞けない。- 重要発言を聞き漏らさない為には
catch(Exception $e){ print $e; }
とする。- PHP版で力尽きました。もし気に入ってもらえたなら、ほかの言語でも事件を解決してみてください
添付:全ソースコード
portpia.php<?php abstract class investigation { abstract function whatName(); abstract function listening(); function say($msg) { print $this->whatName() . " >>> " . $msg . "\n"; } } class yamakawa extends investigation { function whatName() { return '山川(殺害された人) '; } function listening() { $this->say("社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました!"); try { $this->deepListening(); } catch (Exception $e) { throw new Exception("実は、昔、詐欺をやっていたという過去があります", 0, $e); } } function deepListening() { throw new Exception("実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました"); } } class fumie extends investigation { function whatName() { return '文江(山川の秘書) '; } function listening() { $this->say("山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです!"); try { $this->deepListening(); } catch (Exception $e) { throw new Exception("実は、ヤスは私の兄です", 0, $e); } } function deepListening() { throw new Exception("実は・・・兄の犯罪に加担していました"); } } class yasu extends investigation { function whatName() { return 'ヤス(捜査している人)'; } function listening() { $this->say("山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい!"); try { $this->deepListening(); } catch (Exception $e) { throw new Exception("実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります", 0, $e); } } function deepListening() { throw new Exception("実は・・・詐欺の黒幕は山川社長と知り、復讐のため私が殺害しました"); } } $objs = []; $objs[] = new yamakawa(); $objs[] = new fumie(); $objs[] = new yasu(); print "\n"; print "***\n"; print "*** 聴取1回目:自己紹介(Exceptionを完全に握りつぶす)\n"; print "***\n"; foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // 吐かないよう握りつぶす ; } } print "\n"; print "***\n"; print "*** 聴取2回目:事情を聴く(直近のExceptionのメッセージ1つだけ吐かせる)\n"; print "***\n"; foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // 1つだけ吐かせる $obj->say($e->getMessage()); } } print "\n"; print "***\n"; print "*** 聴取3回目:真実を吐かせる(ネストしているExceptionのメッセージも含め全部吐かせる)\n"; print "***\n"; foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // 全部吐かせる do { $obj->say($e->getMessage()); } while($e = $e->getPrevious()); } } print "\n"; print "***\n"; print "*** 聴取の総括:どこで吐いたのか(Exceptionのメッセージ以外の情報も全部吐かせる)\n"; print "***\n"; foreach ($objs as $obj) { print "\n"; try { $obj->listening(); } catch (Exception $e) { // どこで吐いたのかも含めて、全部吐かせる // print $e."\n"; でいいのだけど、出力が見づらいので行頭を少し加工 print preg_replace('/^/m', ' |', $e)."\n"; } }添付:全実行結果
PHP7.2で動作確認しましたが、他のバージョンでも動くと思います。
$ php --version PHP 7.2.15-0ubuntu0.18.04.1 (cli) (built: Feb 8 2019 14:54:22) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.2.15-0ubuntu0.18.04.1, Copyright (c) 1999-2018, by Zend Technologies $ php portopia.php *** *** 聴取1回目:自己紹介(Exceptionを完全に握りつぶす) *** 山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい! *** *** 聴取2回目:事情を聴く(直近のExceptionのメッセージ1つだけ吐かせる) *** 山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! 山川(殺害された人) >>> 実は、昔、詐欺をやっていたという過去があります 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! 文江(山川の秘書) >>> 実は、ヤスは私の兄です ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい! ヤス(捜査している人) >>> 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります *** *** 聴取3回目:真実を吐かせる(ネストしているExceptionのメッセージも含め全部吐かせる) *** 山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! 山川(殺害された人) >>> 実は、昔、詐欺をやっていたという過去があります 山川(殺害された人) >>> 実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! 文江(山川の秘書) >>> 実は、ヤスは私の兄です 文江(山川の秘書) >>> 実は・・・兄の犯罪に加担していました ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい! ヤス(捜査している人) >>> 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります ヤス(捜査している人) >>> 実は・・・詐欺の黒幕は山川社長と知り、復讐のため私が殺害しました *** *** 聴取の総括:どこで吐いたのか(Exceptionのメッセージ以外の情報も全部吐かせる) *** 山川(殺害された人) >>> 社長やってます。文江を秘書に雇ってます。ついさっき誰かに殺されました! |Exception: 実は・・・後悔しています。罪滅ぼしのため文江を秘書に雇いました in /home/ubuntu/20190227/portopia.php:31 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(23): yamakawa->deepListening() |#1 /home/ubuntu/20190227/portopia.php(137): yamakawa->listening() |#2 {main} | |Next Exception: 実は、昔、詐欺をやっていたという過去があります in /home/ubuntu/20190227/portopia.php:25 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(137): yamakawa->listening() |#1 {main} 文江(山川の秘書) >>> 山川社長の秘書です。社長の遺体の第一発見者は私と、社長の甥の川村さんです! |Exception: 実は・・・兄の犯罪に加担していました in /home/ubuntu/20190227/portopia.php:54 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(46): fumie->deepListening() |#1 /home/ubuntu/20190227/portopia.php(137): fumie->listening() |#2 {main} | |Next Exception: 実は、ヤスは私の兄です in /home/ubuntu/20190227/portopia.php:48 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(137): fumie->listening() |#1 {main} ヤス(捜査している人) >>> 山川社長殺害事件の捜査やってます。社長と揉めていた、山川の甥の川村が怪しい! |Exception: 実は・・・詐欺の黒幕は山川社長と知り、復讐のため私が殺害しました in /home/ubuntu/20190227/portopia.php:77 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(69): yasu->deepListening() |#1 /home/ubuntu/20190227/portopia.php(137): yasu->listening() |#2 {main} | |Next Exception: 実は、親の会社が詐欺にあい倒産。両親自殺。妹と生き別れの過去があります in /home/ubuntu/20190227/portopia.php:71 |Stack trace: |#0 /home/ubuntu/20190227/portopia.php(137): yasu->listening() |#1 {main}
- 投稿日:2019-02-28T23:23:23+09:00
オブジェクト指向とはなんぞや?
- 投稿日:2019-02-28T18:24:22+09:00
MongoDBを扱うために最低限知っておきたいこと
概要
以下観点でまとめたMongoDBの操作系簡易リファレンスです
- とりあえずこれだけ知っていればMongoDBをデータストアとしたアプリ開発に入れる
- 開発Teamメンバーに知っておいてほしいこと
前提
- MongoDBがセットアップ済みであること
- 参照、更新コマンド、クエリセレクタは最低限使えること
環境
使用環境 バージョン CentOS 7.6 MongoDB server 4.0.5 状態確認
データベース一覧
> show dbs admin 0.000GB config 0.000GB local 0.000GBデータベース切り替え(新規作成)
> use testdb switched to db testdbコレクション一覧
> show collections company department user初期化関連
コレクションtruncate
> db.user.remove({})コレクション削除
> db.user.drop()データベース削除
drop対象を指定した状態で実行
> use testdb > db.dropDatabase()設計・パフォーマンスチューニング
問い合わせプラン確認
> db.user.find({name:"koda"}).explain()index確認
> db.user.getIndexes()index作成(通常)
value値 1:昇順、-1:降順
> db.user.createIndex({name: 1})index作成(ユニーク)
> db.user.createIndex({name: 1}, {unique: true})index作成(マルチフィールド)
> db.user.createIndex({name: 1, sex: 1})ヘルプ・コマンド確認
ヘルプ
> help db.help() help on db methods db.mycoll.help() help on collection methods sh.help() sharding helpers (省略)コマンド確認
tab補完が使える
DBコマンド
> db. db.adminCommand( db.fsyncLock( db.getWriteConcern( db.revokePrivilegesFromRole( db.aggregate( db.fsyncUnlock( db.grantPrivilegesToRole( (省略)コレクションコマンド
> db.user. db.user.addIdIfNeeded( db.user.getCollection( db.user.mapReduce( db.user.aggregate( db.user.getDB( db.user.propertyIsEnumerable (省略)コマンドライン実行
直接実行
$ echo $SHELL /bin/bash $ mongo --host localhost --quiet testdb --eval 'db.user.find()' { "_id" : ObjectId("5c7768915fd5d6ec50b38477"), "uid" : 1, "name" : "koda", "sex" : "男" } { "_id" : ObjectId("5c7768915fd5d6ec50b38478"), "uid" : 2, "name" : "mori", "sex" : "男" } { "_id" : ObjectId("5c7768915fd5d6ec50b38479"), "uid" : 3, "name" : "kubo", "sex" : "男" }jsを実行
jsファイルを準備
find.jsvar users = db.user.find(); users.forEach(function(user) { if (user) { print(user._id + '\t' + user.name + '\t' + user.sex); } });実行
$ mongo --quiet testdb ./find.js 5c7768915fd5d6ec50b38477 koda 男 5c7768915fd5d6ec50b38478 mori 男 5c7768915fd5d6ec50b38479 kubo 男バックアップ、レストア
ダンプ
$ mongodump --archive=/tmp/testdb.20190228.gz --gzip --db testdbレストア
対象DBがない状態で実行
> show dbs admin 0.000GB config 0.000GB local 0.000GB$ mongorestore --gzip --archive=/tmp/testdb.20190228.gz --db testdbエクスポート・インポート
エクスポート
$ mongoexport --db testdb --collection user --out /tmp/user.jsonインポート
$ mongoimport --db testdb_new --collection user --file /tmp/user.jsonシーケンス
オートインクリメント用の関数を作成しRDBのシーケンスのように利用する
> use testdbcountersコレクションを作成しておく
> db.counters.insert( { _id: "userid", seq: 0 } )> db.counters.find() { "_id" : "userid", "seq" : 0 }オートインクリメント関数を作成
- 永続的に使える状態にするため、db.system.jsに登録しておく
- Mongo shell上で定義した変数やfunctionはログオフで消えてしまうため
> db.system.js.save({_id:'my_seq', value:function (name) { var ret = db.counters.findAndModify( { query: { _id: name }, update: { $inc: { seq: 1 } }, new: true } ); return ret.seq; } });確認のためにログオフ
> exit bye再接続
$ mongo> use testdb登録したスクリプトをロード
> db.loadServerScripts()シーケンス取得
> my_seq('userid') 1 > my_seq('userid') 2 > my_seq('userid') 3実際に使う場合はこんな感じ
> db.users.insert({_id: my_seq('userid'), name: "seq_test_user"} ) > db.users.find() { "_id" : 4, "name" : "seq_test_user" }ユーザ認証
スーパーユーザを準備したうえでアプリ用ユーザを作成するイメージ
ユーザ管理者の登録
/etc/mongod.conf#security:
> use admin> db.createUser({ user:"admin", pwd:"manager", roles:[{ role:"userAdminAnyDatabase", db:"admin" }] })再起動
認証設定を有効にする
/etc/mongod.confsecurity: authorization: enabled# systemctl restart mongod
アプリ用ユーザ登録
ユーザ管理者で再接続
$ mongo -u "admin" -p "manager" --authenticationDatabase="admin"> use testdb> db.createUser( { user: "airuser", pwd:"airuser", roles:[ {role:"readWrite", db:"testdb"} ] } )cf. Built-In Roles
ロール変更
> use testdb> db.updateUser( "airuser", { roles:[ {role: "readWrite",db: "testdb"} ] } )登録済みユーザ確認
> use admin> db.system.users.find()接続確認
アプリ用ユーザで接続
$ mongo --host localhost -u "airuser" -p "airuser" --authenticationDatabase="testdb"許可DBだけ見える
> show dbs testdb 0.000GBおまけ
PHPでアクセス
ユーザ認証ありの状態で検証
ファイル準備
mongo.php<?php // DBへ接続 $mongo = new MongoClient("mongodb://localhost:27017/testdb", array("username" => "airuser", "password" => "airuser")); // データベースを指定 $db = $mongo->selectDB("testdb"); // コレクションを指定 $coll = $db->selectCollection("user"); // コレクションのドキュメントを取得 $docs = $coll->find(array('uid' => 1)); // 表示 foreach ($docs as $id => $obj) { print_r($obj); } ?>実行
$ php mongo.php Array ( [_id] => MongoId Object ( [$id] => 5c7787e73cf5a95585ea27f7 ) [uid] => 1 [name] => koda [sex] => 男 )フィールドを縦に展開
利用頻度が高いですよね
> db.user.find().pretty() { "_id" : ObjectId("5c7768915fd5d6ec50b38477"), "uid" : 1, "name" : "koda", "sex" : "男" }
- cf.
- MySQL: SQLの最後に \G
- PostgreSQL: メタコマンド \x
参考
- 投稿日:2019-02-28T18:10:07+09:00
switch文の比較、JavaScriptは'==='、PHPは’==’
同じswitch文を使って出力結果の違いを比較しよう
下はJSのコードですが、PHPで考える場合は読み替えてください。すなわち、
num
を$num
、console.log(~)
はecho ~
と読み替えてください。JavaScriptとPHP// A switch(num) { case 5: console.log('整数の5'); break; default: console.log('該当なし'); break; } // B switch(num) { case '5': console.log('文字列の5'); break; default: console.log('該当なし'); break; }結果
なぜこうなるのか
式を比較するときに、JavaScriptは厳密等価演算子(
===
)を使った厳密な比較を行い、PHPは等価演算子(==
)を使った緩やかな比較を行うから。JavaScriptのソース
switch文はまず始めに式を評価します。次に (厳密等価演算子===を使用して) 式が入力式の結果と評価される値が等しい最初の case 節を探し、…
switch - JavaScript | MDNPHPのソース
switch/case が行うのは、 緩やかな比較 であることに注意しましょう。
PHP: switch - ManualPHPでも厳密に比較したい
PHP// A switch(true) { case $num === 5: echo '整数の5'; break; default: echo '該当なし'; break; } // B switch(true) { case $num === '5': echo '文字列の5'; break; default: echo '該当なし'; break; }このように書くと厳密な比較になります。結果は以下の通り。なお、「JavaScript」と「PHP」の結果は、同じswitch文を使って出力結果の違いを比較しようのところで紹介しかコードを使ったときの結果で、「PHP(厳密)」はここで紹介したコードを使ったときの結果です。
- 投稿日:2019-02-28T15:09:21+09:00
PHPで日本語の曜日を使いたい
class MyDateTime extends \DateTime { public function format ($f) { $s = parent::format($f); $daynames = ['火', '水', '木', '金', '土', '日', '月']; $dayname = $daynames[parent::format('w')]; return str_replace('x', $dayname, $s); } }print (new MyDateTime())->format('Ymd(x)');簡単だけどコードがぐっとシンプルになってとても便利なのでよく使う
多言語対応も簡単class MyDateTime extends \DateTime { const DAYNAME_SET = [ 'ja' => ['日', '月', '火', '水', '木', '金', '土'], 'ru' => ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'] ]; public function format ($f, $lang = 'ja') { $s = parent::format($f); $daynames = self::DAYNAME_SET[$lang]; $dayname = $daynames[parent::format('w')]; return str_replace('x', $dayname, $s); } }
- 投稿日:2019-02-28T13:46:06+09:00
PhpStorm(IntelliJ IDEA)でLaravelプロジェクトの新規作成からローカルデバッグ環境構築まで
動作環境
Windows 7
IntelliJ IDEA 2018.3.4
PHP 7.3.2
Xdebug v2.7.0RC2
Laravel Framework 5.8.2前提条件
- IntelliJ IDEAにPHPプラグインがインストールされていること
背景
普段Unity+Riderで開発をしています
デバッグ時にはデバッグボタン押すだけでブレークポイントが有効になりステップ実行が可能になります
効率的にデバッグをこなす上でブレークポイントの有効可は必須なので今回の環境構築を行いました
1クリックでローカルサーバの起動→デバッグ開始できるようにしますPHPのインストール
PHP download
- Cドライブ直下に展開 (C:\php-7.3.2)
- php.ini-developmentをコピーしてphp.iniにリネーム
- php.iniないの下記設定のコメントアウトを解除;extension_dir = "ext" ;extension=openssl ;extension=mbstring ;extension=pdo_mysqlXdebugのインストール
php --info
で出力される情報をコピーして上記サイトに貼り付けAnalyse my phpinfo() output
をクリック- 環境に合わせたxdebugのダウンロードリンクが出てくるのでダウンロード(自分の環境ではphp_xdebug-2.7.0RC2-7.3-vc15-x86_64.dll)
- C:\php-7.3.2\ext配下にコピー
php.iniに
zend_extension = php_xdebug-2.7.0RC2-7.3-vc15-x86_64.dll
を追記ext\php_xdebug~にするとデバッグ時にエラーが出たのでextは抜きました
PhpStorm(IntelliJ IDEA)でLaravelプロジェクトの新規作成
Project name: laravel-sample
composer.pharにチェック
Download composer.pharにチェック
PHP Interpriter: PHP(7.3.2)を選択
Filter package: laravel/laravelを入力して一覧から選択してFinish
起動確認
php artisan key:generate # APP_KEYの生成 php artisan serveでローカルサーバを起動しlocalhost:8000にアクセスして起動できることを確認します。
Built-in Web Serverの設定
php artisan serve
でローカルサーバ起動しましたがXdebugを使ってデバッグでするためPhpStormで設定を行います
- Run -> Edit Configurations...
- PHP Built-in Web Serverを追加 - Name: Build-in Web Server - Host: localhost - Port: 8000 - Document root:<プロジェクトのディレクトリ>/public - Interpreter options: -dxdebug.remote_enable=1 -dxdebug.remote_autostart=1 -dxdebug.remote_host=localhost -dxdebug.remote_port=9000
xdebugの設定
File -> Settings -> PHP
File -> Settings -> PHP -> Debug -> Xdegug
- Debug port: 9000
File -> Settings -> PHP -> Servers
デバッグ実行
- Run -> RunでBuilt-in Web Serverで設定したサーバを選択して実行
または
ブレークポイントを仕掛けてhttp://localhost:8000にアクセスすると下の画像のようにステップ実行が可能となっているはずです
受話器のマークが赤枠のようになっていないことに気をつけてください
良いデバッグライフを(๑•̀ㅂ•́)و✧
- 投稿日:2019-02-28T13:20:25+09:00
【laravel5.7】MultiAuthにパスワードリセットを追加する
MultiAuthにパスワードリセットを追加する実装メモ
【laravel5.7】 MultiAuthログインの続きとして書きます。やりたいこと
User/Adminそれぞれにパスワードリセットを実装したい
環境
MacOS 10.14.3
VisualStudio
laravel 5.7Admin側の設定
auth.php
リセット用のtokenを保存するテーブルが必要なので、auth.phpで設定します。
config/auth.php'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, ], + 'admins' => [ + 'provider' => 'admins', + 'table' => 'password_resets', + 'expire' => 60, + ], ],
Controller
ForgotPasswordController.php
app/Http/Controllers/Admin/Auth/ForgetPasswordController.php<?php +namespace App\Http\Controllers\Admin\Auth; use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\SendsPasswordResetEmails; class ForgotPasswordController extends Controller { /* |-------------------------------------------------------------------------- | Password Reset Controller |-------------------------------------------------------------------------- | | This controller is responsible for handling password reset emails and | includes a trait which assists in sending these notifications from | your application to your users. Feel free to explore this trait. | */ use SendsPasswordResetEmails; /** * Create a new controller instance. * * @return void */ public function __construct() { + $this->middleware('guest:admin'); } + public function showLinkRequestForm() + { + return view('admin.auth.passwords.email'); + } + protected function guard() + { + return \Auth::guard('admin'); + } + public function broker() + { + return \Password::broker('admins'); + } }ResetPasswordController.php
app/Http/Controllers/Admin/Auth/ResetPasswordController.php<?php +namespace App\Http\Controllers\Admin\Auth; use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\ResetsPasswords; +use Illuminate\Http\Request; class ResetPasswordController extends Controller { /* |-------------------------------------------------------------------------- | Password Reset Controller |-------------------------------------------------------------------------- | | This controller is responsible for handling password reset requests | and uses a simple trait to include this behavior. You're free to | explore this trait and override any methods you wish to tweak. | */ use ResetsPasswords; /** * Where to redirect users after resetting their password. * * @var string */ + protected $redirectTo = '/admin/home'; /** * Create a new controller instance. * * @return void */ public function __construct() { + $this->middleware('guest:admin'); } + public function showResetForm(Request $request, $token = null) + { + return view('admin.auth.passwords.reset')->with(['token' => $token,'email' => $request->email]); + } + protected function guard() + { + return \Auth::guard('admin'); + } + public function broker() + { + return \Password::broker('admins'); + } }Views
email.blade.php
resources/views/admin/auth/passwords/email.blade.php+@extends('layouts.admin.auth') @section('auth') <div class="container"> <div class="row justify-content-center"> <div class="col-md-6"> <div class="card mx-4"> <div class="card-body p-4"> <h2>{{ __('Reset Password') }}</h2> <p class="text-muted">リセットURLを送信します。</p> @if (session('status')) <div class="alert alert-success" role="alert"> {{ session('status') }} </div> @endif + <form method="POST" action="{{ route('admin.password.email') }}"> @csrf <div class="col-auto"> <div class="input-group mb-3"> <div class="input-group-prepend"> <div class="input-group-text">@</div> </div> <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required> </div> @if ($errors->has('email')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('email') }}</strong> </span> @endif <button type="submit" class="btn btn-primary"> {{ __('Send Password Reset Link') }} </button> </div> </form> </div> </div> </div> </div> </div> @endsectionreset.blade.php
resources/views/admin/auth/passwords/reset.blade.php+@extends('layouts.admin.auth') @section('auth') <div class="container"> <div class="row justify-content-center"> <div class="col-md-6"> <div class="card mx-4"> <div class="card-body p-4"> <h2>{{ __('Reset Password') }}</h2> {{-- <div class="card-body"> --}} + <form method="POST" action="{{ route('admin.password.update') }}"> @csrf <input type="hidden" name="token" value="{{ $token }}"> <div class="col-auto"> <div class="input-group mb-3"> <div class="input-group-prepend"> <div class="input-group-text">Email</div> </div> <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ $email ?? old('email') }}" required autofocus> </div> @if ($errors->has('email')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('email') }}</strong> </span> @endif </div> <div class="col-auto"> <div class="input-group mb-3"> <div class="input-group-prepend"> <div class="input-group-text"><i class="fas fa-key"></i></div> </div> <input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required> @if ($errors->has('password')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('password') }}</strong> </span> @endif </div> </div> <div class="col-auto"> <div class="input-group mb-3"> <div class="input-group-prepend"> <div class="input-group-text"><i class="fas fa-key"></i></div> </div> <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required> </div> </div> <div class="form-group row mb-0"> <div class="col-md-6 offset-md-4"> <button type="submit" class="btn btn-primary"> {{ __('Reset Password') }} </button> </div> </div> </form> {{-- </div> --}} </div> </div> </div> </div> </div> </div> @endsectionルーティング
web.php
routes/web.php// Admin Route::group(['namespace' => 'Admin','prefix'=>'admin'],function(){ // home Route::get('home','HomeController@index')->name('admin.home'); // login logout Route::get('login','Auth\LoginController@showLoginForm')->name('admin.login'); Route::post('login','Auth\LoginController@login')->name('admin.login'); Route::post('logout','Auth\LoginController@logout')->name('admin.logout'); // register Route::get('register','Auth\RegisterController@showRegisterForm')->name('admin.register'); Route::post('register','Auth\RegisterController@register')->name('admin.register'); + // password resets + Route::post('password/email','Auth\ForgotPasswordController@sendResetLinkEmail')->name('admin.password.email'); + Route::post('password/reset','Auth\ResetPasswordController@reset')->name('admin.password.request'); + Route::get('password/reset','Auth\ForgotPasswordController@showLinkRequestForm')->name('admin.password.update'); + Route::get('password/reset/{token}','Auth\ResetPasswordController@showResetForm')->name('admin.password.reset'); });
送信機能のオーバーライド
AdminPasswordResetNotification.phpを作ります。
terminalphp artisan make:notification AdminPasswordResetNotification
app/Notifications/AdminPasswordResetNotification.php<?php namespace App\Notifications; use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification; use Illuminate\Notifications\Messages\MailMessage; class AdminPasswordResetNotification extends ResetPasswordNotification { public function toMail($notifiable) { return (new MailMessage) ->line('ADMINパスワードリセット用メール.') ->action('Reset Password', url(config('url').route('admin.password.reset', $this->token, false))) ->line('If you did not request a password reset, no further action is required.'); } }Admin.phpの変更
AdminPasswordResetNotificationがメールで送信されるようにModelに設定します。
app/Admin.php<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Model; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; +use App\Notifications\AdminPasswordResetNotification; class Admin extends Authenticatable { use Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password','api_token' ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; + public function sendPasswordResetNotification($token) + { + $this->notify(new AdminPasswordResetNotification($token)); + } }動作確認
http://127.0.0.1:8000/admin/login
こんな感じで動けばOK
User側の設定
Adminとやることは変わりません
Controller
ForgotPasswordController.php
app/Http/Controllers/User/Auth/ForgetPasswordController.php<?php +namespace App\Http\Controllers\User\Auth; use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\SendsPasswordResetEmails; class ForgotPasswordController extends Controller { /* |-------------------------------------------------------------------------- | Password Reset Controller |-------------------------------------------------------------------------- | | This controller is responsible for handling password reset emails and | includes a trait which assists in sending these notifications from | your application to your users. Feel free to explore this trait. | */ use SendsPasswordResetEmails; /** * Create a new controller instance. * * @return void */ public function __construct() { + $this->middleware('guest:user'); } + public function showLinkRequestForm() + { + return view('user.auth.passwords.email'); + } + protected function guard() + { + return \Auth::guard('user'); + } + public function broker() + { + return \Password::broker('users'); + } }ResetPasswordController.php
app/Http/Controllers/User/Auth/ResetPasswordController.php<?php +namespace App\Http\Controllers\User\Auth; use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\ResetsPasswords; +use Illuminate\Http\Request; class ResetPasswordController extends Controller { /* |-------------------------------------------------------------------------- | Password Reset Controller |-------------------------------------------------------------------------- | | This controller is responsible for handling password reset requests | and uses a simple trait to include this behavior. You're free to | explore this trait and override any methods you wish to tweak. | */ use ResetsPasswords; /** * Where to redirect users after resetting their password. * * @var string */ + protected $redirectTo = '/user/home'; /** * Create a new controller instance. * * @return void */ public function __construct() { + $this->middleware('guest:user'); } + public function showResetForm(Request $request, $token = null) + { + return view('user.auth.passwords.reset')->with(['token' => $token,'email' => $request->email]); + } + protected function guard() + { + return \Auth::guard('user'); + } + public function broker() + { + return \Password::broker('users'); + } }Views
email.blade.php
resources/views/user/auth/passwords/email.blade.php+@extends('layouts.user.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('Reset Password') }}</div> <div class="card-body"> @if (session('status')) <div class="alert alert-success" role="alert"> {{ session('status') }} </div> @endif + <form method="POST" action="{{ route('user.password.email') }}"> @csrf <div class="form-group row"> <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label> <div class="col-md-6"> <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required> @if ($errors->has('email')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('email') }}</strong> </span> @endif </div> </div> <div class="form-group row mb-0"> <div class="col-md-6 offset-md-4"> <button type="submit" class="btn btn-primary"> {{ __('Send Password Reset Link') }} </button> </div> </div> </form> </div> </div> </div> </div> </div> @endsectionreset.blade.php
resources/views/user/auth/passwords/reset.blade.php+@extends('layouts.user.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('Reset Password') }}</div> <div class="card-body"> + <form method="POST" action="{{ route('user.password.update') }}"> @csrf <input type="hidden" name="token" value="{{ $token }}"> <div class="form-group row"> <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label> <div class="col-md-6"> <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ $email ?? old('email') }}" required autofocus> @if ($errors->has('email')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('email') }}</strong> </span> @endif </div> </div> <div class="form-group row"> <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label> <div class="col-md-6"> <input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required> @if ($errors->has('password')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('password') }}</strong> </span> @endif </div> </div> <div class="form-group row"> <label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label> <div class="col-md-6"> <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required> </div> </div> <div class="form-group row mb-0"> <div class="col-md-6 offset-md-4"> <button type="submit" class="btn btn-primary"> {{ __('Reset Password') }} </button> </div> </div> </form> </div> </div> </div> </div> </div> @endsectionルーティング
web.php
routes/web.php// User Route::group(['namespace' => 'User','prefix'=>'user'],function(){ Route::get('/',function(){ return redirect()->to('user/home'); })->name('user'); // home Route::get('home','HomeController@index')->name('user.home'); // login lgoout Route::get('login','Auth\LoginController@showLoginForm')->name('user.login'); Route::post('login','Auth\LoginController@login')->name('user.login'); Route::post('logout','Auth\LoginController@logout')->name('user.logout'); // register Route::get('register','Auth\RegisterController@showRegisterForm')->name('user.register'); Route::post('register','Auth\RegisterController@register')->name('user.register'); // emailverify Route::middleware('throttle:6,1')->get('email/resend','Auth\VerificationController@resend')->name('user.verification.resend'); Route::middleware('throttle:6,1')->get('email/verify','Auth\VerificationController@show')->name('user.verification.notice'); Route::middleware('signed')->get('email/verify/{id}','Auth\VerificationController@verify')->name('user.verification.verify'); + // password reset + Route::post('password/email','Auth\ForgotPasswordController@sendResetLinkEmail')->name('user.password.email'); + Route::post('password/reset','Auth\ResetPasswordController@reset')->name('user.password.request'); + Route::get('password/reset','Auth\ForgotPasswordController@showLinkRequestForm')->name('user.password.update'); + Route::get('password/reset/{token}','Auth\ResetPasswordController@showResetForm')->name('user.password.reset'); });
送信機能のオーバーライド
UserPasswordResetNotification.phpを作ります。
terminalphp artisan make:notification UserPasswordResetNotification
app/Notifications/UserPasswordResetNotification.php<?php namespace App\Notifications; use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification; use Illuminate\Notifications\Messages\MailMessage; class UserPasswordResetNotification extends ResetPasswordNotification { public function toMail($notifiable) { return (new MailMessage) ->line('USERパスワードリセット用メール.') ->action('Reset Password', url(config('url').route('user.password.reset', $this->token, false))) ->line('If you did not request a password reset, no further action is required.'); } }User.phpの変更
UserPasswordResetNotificationがメールで送信されるようにModelに設定します。
app/User.php<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; +use App\Notifications\UserPasswordResetNotification; class User extends Authenticatable implements MustVerifyEmail { use Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; + public function sendPasswordResetNotification($token) + { + $this->notify(new UserPasswordResetNotification($token)); + } }動作確認
http://127.0.0.1:8000/user/login
Adminと同じように動けばOKです。
以上です。
- 投稿日:2019-02-28T12:06:13+09:00
【laravel5.7】 MultiAuthにEmailVerification
MultiAuthにEmailVerificationを対応させる実装メモ。
【laravel5.7】 MultiAuthログインの続きとして書きます。MultiAuthなしの実装メモは別記事で書いているので、参考になれば、、、
【laravel5.7】 Email Verificationの使い方やりたいこと
MultiAuth(Admin/User)のUserのみにEmail Verificationを対応させたい。
環境
MacOS 10.14.3
VisualStudio
laravel 5.7Email Verificationを使えるようにする
MustVerifyEmailの追加
app/User.php<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; +class User extends Authenticatable implements MustVerifyEmail {
ルーティング設定
routes/web.php<?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ use Illuminate\Http\Request; Route::get('/', function () { return view('welcome'); }); Auth::routes(); Route::get('/home', 'HomeController@index')->name('home'); // User Route::group(['namespace' => 'User','prefix'=>'user'],function(){ + Route::get('/',function(){ + return redirect()->to('user/home'); + })->name('user'); // home Route::get('home','HomeController@index')->name('user.home'); // login lgoout Route::get('login','Auth\LoginController@showLoginForm')->name('user.login'); Route::post('login','Auth\LoginController@login')->name('user.login'); Route::post('logout','Auth\LoginController@logout')->name('user.logout'); // register Route::get('register','Auth\RegisterController@showRegisterForm')->name('user.register'); Route::post('register','Auth\RegisterController@register')->name('user.register'); + // emailverify + Route::middleware('throttle:6,1')->get('email/resend','Auth\VerificationController@resend')->name('user.verification.resend'); + Route::middleware('throttle:6,1')->get('email/verify','Auth\VerificationController@show')->name('user.verification.notice'); + Route::middleware('signed')->get('email/verify/{id}','Auth\VerificationController@verify')->name('user.verification.verify'); }); // Admin 以下省略単体ログインのEmailVerificationの
Auth:routes(['verify'=>true]);
を
MultiAuthでは上の(//emailverify以下
の部分)のように一つずつ書いていきます。VerificationController.php
app/Http/Controllers/User/Auth/VerificationController.php<?php +namespace App\Http\Controllers\User\Auth; use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\VerifiesEmails; +use Illuminate\Http\Request; class VerificationController extends Controller { /* |-------------------------------------------------------------------------- | Email Verification Controller |-------------------------------------------------------------------------- | | This controller is responsible for handling email verification for any | user that recently registered with the application. Emails may also | be re-sent if the user didn't receive the original email message. | */ use VerifiesEmails; /** * Where to redirect users after verification. * * @var string */ + protected $redirectTo = '/user/home'; /** * Create a new controller instance. * * @return void */ public function __construct() { + $this->middleware('auth:user'); $this->middleware('signed')->only('verify'); $this->middleware('throttle:6,1')->only('verify', 'resend'); } + public function show(Request $request) + { + return $request->user()->hasVerifiedEmail() + ? redirect($this->redirectPath()) + : view('user.auth.verify'); + } }HomeController.php
app/Http/Controllers/User/HomeController.phppublic function __construct() { $this->middleware('auth:user'); + $this->middleware('allVerified'); }
Middleware
AllEnsureEmailIsVerified.phpを作成します
terminalphp artisan make:middleware AllEnsureEmailIsVerified
app/Http/Middleware/AllEnsureEmailIsVerified.php<?php namespace App\Http\Middleware; use Closure; use Illuminate\Support\Facades\Redirect; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Support\Facades\Auth; class AllEnsureEmailIsVerified { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { $guards = array_keys(config('auth.guards')); foreach($guards as $guard) { if($guard == 'user') { if (Auth::guard($guard)->check()) { if (! Auth::guard($guard)->user() || (Auth::guard($guard)->user() instanceof MustVerifyEmail && ! Auth::guard($guard)->user()->hasVerifiedEmail())) { // dd('ddd'); return $request->expectsJson() ? abort(403, 'Your email address is not verified.') : Redirect::route('user.verification.notice'); } } } } return $next($request); } }Kernel.php
app/Http/Kernel.phpprotected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'allVerified' => \App\Http\Middleware\AllEnsureEmailIsVerified::class, ];
メール内容をカスタマイズ
Notificationsの作成
terminalphp artisan make:notification UserVerifyEmailNotification
app/Notifications/UserVerifyEmailNotification.php<?php namespace App\Notifications; use Illuminate\Auth\Notifications\VerifyEmail as VerifyEmailNotification; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\Lang; class UserVerifyEmailNotification extends VerifyEmailNotification { public function toMail($notifiable) { if (static::$toMailCallback) { return call_user_func(static::$toMailCallback, $notifiable); } return (new MailMessage) ->subject(Lang::getFromJson('USERメール確認')) ->line(Lang::getFromJson('クリックして認証してください.')) ->action( Lang::getFromJson('メール認証'), $this->verificationUrl($notifiable) ) ->line(Lang::getFromJson('もしこのメールに覚えが無い場合は破棄してください。')); } protected function verificationUrl($notifiable) { return URL::temporarySignedRoute( 'user.verification.verify', Carbon::now()->addMinutes(60), ['id' => $notifiable->getKey()] ); } }User.php
UserVerifyEmailNotificationがメールで送信されるようにModelに設定します。
app/User.php<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; +use App\Notifications\UserVerifyEmailNotification; class User extends Authenticatable implements MustVerifyEmail { use Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; + public function sendEmailVerificationNotification() + { + $this->notify(new UserVerifyEmailNotification); + } }動作確認
User
http://127.0.0.1:8000/user/register から新しくアカウントを登録します。こうなれば成功です。
まだ英語の部分があるので、その辺の設定は後々設定しようと思います。Adminでも実装したい場合はこれと同様に編集すればOK。
とりあえず以上です。
- 投稿日:2019-02-28T12:06:13+09:00
【laravel5.7】 MultiAuthにEmailVerificationを対応させる
MultiAuthにEmailVerificationを対応させる実装メモ。
【laravel5.7】 MultiAuthログインの続きとして書きます。MultiAuthなしの実装メモは別記事で書いているので、参考になれば、、、
【laravel5.7】 Email Verificationの使い方やりたいこと
MultiAuth(Admin/User)のUserのみにEmail Verificationを対応させたい。
環境
MacOS 10.14.3
VisualStudio
laravel 5.7Email Verificationを使えるようにする
MustVerifyEmailの追加
app/User.php<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; +class User extends Authenticatable implements MustVerifyEmail {
ルーティング設定
routes/web.php<?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ use Illuminate\Http\Request; Route::get('/', function () { return view('welcome'); }); Auth::routes(); Route::get('/home', 'HomeController@index')->name('home'); // User Route::group(['namespace' => 'User','prefix'=>'user'],function(){ + Route::get('/',function(){ + return redirect()->to('user/home'); + })->name('user'); // home Route::get('home','HomeController@index')->name('user.home'); // login lgoout Route::get('login','Auth\LoginController@showLoginForm')->name('user.login'); Route::post('login','Auth\LoginController@login')->name('user.login'); Route::post('logout','Auth\LoginController@logout')->name('user.logout'); // register Route::get('register','Auth\RegisterController@showRegisterForm')->name('user.register'); Route::post('register','Auth\RegisterController@register')->name('user.register'); + // emailverify + Route::middleware('throttle:6,1')->get('email/resend','Auth\VerificationController@resend')->name('user.verification.resend'); + Route::middleware('throttle:6,1')->get('email/verify','Auth\VerificationController@show')->name('user.verification.notice'); + Route::middleware('signed')->get('email/verify/{id}','Auth\VerificationController@verify')->name('user.verification.verify'); }); // Admin 以下省略単体ログインのEmailVerificationの
Auth:routes(['verify'=>true]);
を
MultiAuthでは上の(//emailverify以下
の部分)のように一つずつ書いていきます。VerificationController.php
app/Http/Controllers/User/Auth/VerificationController.php<?php +namespace App\Http\Controllers\User\Auth; use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\VerifiesEmails; +use Illuminate\Http\Request; class VerificationController extends Controller { /* |-------------------------------------------------------------------------- | Email Verification Controller |-------------------------------------------------------------------------- | | This controller is responsible for handling email verification for any | user that recently registered with the application. Emails may also | be re-sent if the user didn't receive the original email message. | */ use VerifiesEmails; /** * Where to redirect users after verification. * * @var string */ + protected $redirectTo = '/user/home'; /** * Create a new controller instance. * * @return void */ public function __construct() { + $this->middleware('auth:user'); $this->middleware('signed')->only('verify'); $this->middleware('throttle:6,1')->only('verify', 'resend'); } + public function show(Request $request) + { + return $request->user()->hasVerifiedEmail() + ? redirect($this->redirectPath()) + : view('user.auth.verify'); + } }HomeController.php
app/Http/Controllers/User/HomeController.phppublic function __construct() { $this->middleware('auth:user'); + $this->middleware('allVerified'); }
Middleware
AllEnsureEmailIsVerified.phpを作成します
terminalphp artisan make:middleware AllEnsureEmailIsVerified
app/Http/Middleware/AllEnsureEmailIsVerified.php<?php namespace App\Http\Middleware; use Closure; use Illuminate\Support\Facades\Redirect; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Support\Facades\Auth; class AllEnsureEmailIsVerified { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { $guards = array_keys(config('auth.guards')); foreach($guards as $guard) { if($guard == 'user') { if (Auth::guard($guard)->check()) { if (! Auth::guard($guard)->user() || (Auth::guard($guard)->user() instanceof MustVerifyEmail && ! Auth::guard($guard)->user()->hasVerifiedEmail())) { // dd('ddd'); return $request->expectsJson() ? abort(403, 'Your email address is not verified.') : Redirect::route('user.verification.notice'); } } } } return $next($request); } }Kernel.php
app/Http/Kernel.phpprotected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'allVerified' => \App\Http\Middleware\AllEnsureEmailIsVerified::class, ];
verify.blade.php
views/user/auth/verify.blade.php+@extends('layouts.user.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('メールアドレスの認証') }}</div> <div class="card-body"> @if (session('resent')) <div class="alert alert-success" role="alert"> {{ __('認証メールを再送信しました。') }} </div> @endif {{ __('メールアドレスの認証をしてください。') }} + {{ __('もしメールを受け取ってないなら、<a href="{{ route('user.verification.resend') }}">ここをクリックしてください</a>。') }} </div> </div> </div> </div> </div> @endsectionメール内容をカスタマイズ
Notificationsの作成
terminalphp artisan make:notification UserVerifyEmailNotification
app/Notifications/UserVerifyEmailNotification.php<?php namespace App\Notifications; use Illuminate\Auth\Notifications\VerifyEmail as VerifyEmailNotification; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\Lang; class UserVerifyEmailNotification extends VerifyEmailNotification { public function toMail($notifiable) { if (static::$toMailCallback) { return call_user_func(static::$toMailCallback, $notifiable); } return (new MailMessage) ->subject(Lang::getFromJson('USERメール確認')) ->line(Lang::getFromJson('クリックして認証してください.')) ->action( Lang::getFromJson('メール認証'), $this->verificationUrl($notifiable) ) ->line(Lang::getFromJson('もしこのメールに覚えが無い場合は破棄してください。')); } protected function verificationUrl($notifiable) { return URL::temporarySignedRoute( 'user.verification.verify', Carbon::now()->addMinutes(60), ['id' => $notifiable->getKey()] ); } }User.php
UserVerifyEmailNotificationがメールで送信されるようにModelに設定します。
app/User.php<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; +use App\Notifications\UserVerifyEmailNotification; class User extends Authenticatable implements MustVerifyEmail { use Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; + public function sendEmailVerificationNotification() + { + $this->notify(new UserVerifyEmailNotification); + } }動作確認
User
http://127.0.0.1:8000/user/register から新しくアカウントを登録します。こうなれば成功です。
まだ英語の部分があるので、その辺の設定は後々設定しようと思います。Adminでも実装したい場合はこれと同様に編集すればOK。
とりあえず以上です。
- 投稿日:2019-02-28T11:49:29+09:00
Php Inspections EA Extended 3.0.11/Ultimate 2.0.13の新機能
Php Inspections (EA Extended)とPhp Inspections (EA Ultimate)はPhpStormのInspection(コード検査)機能をさらに強力なものに拡張してくれるPHPerなら絶対に入れておきたいプラグインです。無償で使えるのがEA Extendedで、それに更に検査ルールを加えたハイエンドの有償版がEA Ultimateです。
本稿では、そのEA Extended 3.0.11(2019/2/23リリース)、EA Ultimate 2.0.13(2019/2/25リリース)に追加された新たなチェックツールを紹介します。
EA Extended/EA Ultimate共通の新機能
PHPUnitのバグとベストプラクティス
検査名: PhpUnit: bugs and best practices
PhpUnit関連のコードを分析してバグを報告し、ベストプラクティスを使用することを提案します。単体テストでの型安全性を強化するために、インスペクションの設定で「assertSameの使用を提案」を有効にすることができます。
assertEquals
よりassertSame
のほうが良く、さらにこの場合、assertSame
よりもassertTrue
のほうが良いということを指摘してくれます。
array_push
の誤用検査名: 'array_push(...)' misused
array_push(...)
呼び出しが$array[] = ...
と同じ意味で使われている場合に、$array[] =
に置き換えることを提案します。そのほうが関数を呼び出すより2倍高速なためです。
array_push
の誤用を検出する様子:誤用は自動で修正してくれる:
EA Ultimate限定の新機能
if-return-returnの単純化
検査名: If-return-return could be simplified
条件に基づいてbool値を返すif()構造を分析し、代わりに条件を返すように単純化します。
if ($conditions) { return true; } return false;上のコードは下のように単純化できます:
return $conditions;繰り返しのメソッド呼び出し
検査名: Repetitive method calls
ループでの繰り返しメソッド呼び出しを報告します。 呼び出し量を減らすために、ローカル変数化を提案します。
疑わしいバイナリオペレーション
検査名: Suspicious binary operations
検査は、
instanceof
演算子など不明確で疑わしい演算子の優先順位、誤ったオペレータ、ハードコードされたブール値、その他のバグを含む可能性があるケースなど、さまざまなケースを報告します。
$a && $b || $c
は($a && $b) || $c
と書いたほうがいいよと提案してくれる様子:疑わしい変数宣言
検査名: Suspicious variable declaration
疑わしい変数の宣言を分析して報告します。
不要なcontinue
検査名: Unnecessary continue statements
削ってもいい
continue
があれば教えてくれます。関連
- 投稿日:2019-02-28T11:41:51+09:00
【php/Laravel】Carbon使って「来月の今日」を求めたいのです
Carbonとは
まず公式をお読みください。
https://carbon.nesbot.com/よくわからん、という場合はこの記事自体読む必要ないと思いますけど、ググって実際の利用ケースを参照してもらうと良いと思います。
何が便利なのか
phpの組み込み関数だけで日付計算しようとすると、結構煩雑な記述になって、後から見た時何しようとしてんのかわからん、っていうのが一番の問題ですよね。
たとえば、1836年1月3日に生まれた坂本龍馬が今生きてたら何歳かな?とか計算しようとしたら・・・誕生日の計算$birthday = "1836-01-03"; // なんかデータベースにでも入ってると仮定して : : $now = date('Ymd'); $bd = date('Ymd', strtotime($birthday)); $age = floor(($now-$bd)/10000); # [結果] # => 183.0のようになります。
特にこの、floor(($now-$bd)/10000)
とかの計算部分がよくわからんですね。
こんなもん標準の関数で用意しとけ、て思うくらいの定型文。
なので、これをCarbon使ってやるとCarbonで誕生日から年齢計算use Carbon\Carbon; : $birthday = "1836-01-03"; : $age = Carbon::parse($birthday)->age; # [結果] # => 183なんと、日付から生成したオブジェクトのageプロパティにアクセスしたら、自動的に年齢が取れてしまいました。
こういう細かく行き届いた機能が山のように用意されています。
便利ですね!これまで(個人的に)問題だったこと
実務でlaravelを使ってシステム組んでいて、日付計算にcarbonを使うようになってずいぶんコード量もバグも減ったと思うのですが、1個だけどうしても解決できない面倒な記述があったのです。
それは 来月 の日付取得。
普通に考えれば、「来月の今日」をもってこようと思ったら
来月の今日$raigetu = Carbon::addMonth()->toDateString();みたいな感じでいい気がしますか?
では1月31日にこうやって日付取り出したらどうなりますか?
やってみてください。うるう年かどうかによりますが、3月2日か3日が返ってきますね。
全然来月じゃない。
こんなロジックで請求書なんか出すシステム組んだ日にはえらいこっちゃですね。
通常、運用的にはこういう場合は翌月の最終日を返してほしいのです。私は通常
来月の今日(2)raigetu = date('Y-m-', strtotime('+1 month', strtotime(date('Y-m-01')))) . date('d');みたいなややこしい書き方をしてました。Carbonのソースコード読むまでは。
このへんは人によっていろんなやり方があると思いますけど、月末で計算するから日数によって月またいだりいろいろしちゃうのであって、1日で計算したら問題ないでしょ?っていうやりかたです。
自分的には定型文ですが、他の人が見たら「なんじゃこりゃ」ってなりかねません。でもCarbonのドキュメント読んでいてもそれっぽい機能が見当たらない。
で、仕方ないのでソースコード詠みこんでみたんですが、なんだかあっさりそれっぽいのが見つかりました。
その名も
addMonthNoOverflowaddMonthNoOverflow の使い方
って程の事はないです。
Carbon// 今日が 1/31だと仮定して、翌月の今日を求めたい場合 $raigetu = Carbon::now()->addMonthNoOverflow()->toDateString(); # [結果] # => "2019-02-28" $raigetu = Carbon::parse("2019-03-31")->addMonthNoOverflow()->toDateString(); #[結果] # => "2019-04-30" // 1か月以上加算したいときは // Carbon::addMonthsNoOverflow(int) // をつかいます。 $hantosi = Carbon::parse("2019-03-31")->addMonthsNoOverflow(6)->toDateString(); #[結果] # => "2019-09-30"あらまぁ・・・便利ですわね、奥さん!
ロジック的には、普通にaddMonthしてから、元の「日」と計算後の「日」が違ったら計算後の日付の「前月の最終日」を返す、っていう組み方みたいです。
速度的なロスを考えると、なんとか1行で書いてしまおうとしている私のやり方よりも賢いかもしれません。何より読みやすいし。というわけで、ますます Carbon万歳 っていう流れになりましたね。
というところで、また次回!
- 投稿日:2019-02-28T10:04:14+09:00
Laravel で外部キー制約の onDelete / onUpdate をマイグレーション後に設定する + 論理削除でハマったこと
やりたいこと
Company
とEmployee
のように親子関係にあるテーブルを作成employee
テーブルにcompany_id
という外部キーを設定- その後、
onDelete
onUpdate
に対する挙動を設定し忘れたことに気づいたので後から設定をしたいマイグレーションファイル
- companies
timestamp_create_companies_table.phpclass CreateCompanyTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('companies', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->timestamps(); $table->softDeletes(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('company'); } }
- employee
timestamp_create_employee_table.phpclass CreateEmployeeTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('employee', function (Blueprint $table) { $table->increments('id'); $table->string('name')->nullable(); $table->integer('company_id')->unsigned(); $table->foreign('company_id')->references('id')->on('companies'); $table->timestamps(); $table->softDeletes(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('employee'); } }マイグレーションは問題なくて、その後も色々なテーブルを追加していった後、onDelete / onUpdate の設定ができていないことに気づいて、「全部ロールバックするのも気が重いし、一部分だけ弄れないかな…」と逡巡して以下の方法を取った。
やったこと
あまりお行儀がよくないかもしれないが、
employee
テーブルのcompany_id
だけを drop するマイグレーションファイルを作り、php artisan migrate
を実行。
その後外部キー制約を再設定するマイグレーションファイルを作って、同様にphp artisan migrate
。
- 外部キーを一旦Drop
timestamp_drop_foreign_key_from_employee.phpclass DropForeignKeyFromEmployee extends Migration { /** * Run the migrations. * * @return void */ public function up() { //外部キー制約を引き剥がす時は dropForeign('テーブル名'_'外部キー名'_foreign); Schema::table('employee', function (Blueprint $table) { $table->dropForeign('employee_company_id_foreign'); }); } }timestamp_reset_foreign_key_to_employee.phppublic function up() { //子テーブルに対象レコードがある場合、親テーブルのレコード削除を禁止 ->onDelete('restrict'); //親テーブルのレコード更新は許可 ->onUpdate('cascade'); Schema::table('employee', function (Blueprint $table) { $table->index('company_id'); $table->foreign('company_id')->references('id')->on('companies') ->onDelete('restrict') ->onUpdate('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('employee', function (Blueprint $table) { $table->dropForeign('employee_company_id_foreign'); }); }問題点
このやり方だとロールバックした時に
DropForeignKeyFromEmployee
エラーで怒られる。なので、最初からなるべくテーブル作成時に onDelete / onUpdate の条件は慎重に考えて設定した方がいい。うまく後付けで設定できる方法があればコメントで教えていただきたいです。個人的に思いつくのがMySQLのコマンドで直接テーブルを書き換える手段だけなので…その他ハマったこと
onDelete('restrict')
を設定したあと、Laravel アプリケーションからCompany::destroy
で実際に削除を試してみたところ、なんと制約に引っかからず普通に削除できてしまった…
どうやらuse SoftDelete
で論理削除をさせている場合、物理削除と違ってdeleted_at
にタイムスタンプを書き込むだけなので制約をすり抜けてしまうらしい…。早めに知っておきたかった。
- 投稿日:2019-02-28T09:01:52+09:00
php-master-changes 2019-02-27
今日は .gdbinit の修正、ReflectionClass::getDefaultProperties() が型付きプロパティに使われた際の不具合の修正、二項演算子が PHP スクリプトのコンパイル時評価とランタイムで同じ結果を返すことのテストを追加する修正があった!
2019-02-27
nikic: Sync types in .gdbinit, improve property dumping
- https://github.com/php/php-src/commit/d73789ef91ebd15f6b72529e78041d366f586611
- [7.4~]
- .gdbinit で、zval 出力時の type が一昨年の 10 月くらいからずれていた問題の修正
- ついでにオブジェクトのダンプでプロパティテーブルが読み込まれておらずクラスエントリからプロパティ情報をたどる際、適切なスロットを参照するよう改善
krakjoe: Fix #77673 ReflectionClass::getDefaultProperties returns spooky array
- https://github.com/php/php-src/commit/1ca9d818b8405159ef96cb974a21714881e1146c
- [7.4~]
- ext/reflection で、ReflectionClass::getDefaultProperties() が型付きプロパティに使われた際、要素の見えない配列を返す場合がある問題の修正
rjhdby: Add runtime_compile_time_binary_operands.phpt
- https://github.com/php/php-src/commit/fcfec9102b638298f2a137ef915e29dc23ddd6f2
- 二項演算子が PHP スクリプトのコンパイル時評価とランタイムで同じ結果を返すことのテストを追加
- PR:3885
bwoebi: Uncomment cleanup for runtime_compile_time_binary_operands.phpt
- https://github.com/php/php-src/commit/a72c74162491f20685ef6805af432717cbcffe90
- [7.4~]
- ↑のテストで一時ファイルの掃除がコメントアウトされていたのを修正
- 投稿日:2019-02-28T08:41:46+09:00
AWS LambdaのPHP関数のログ出力について
はじめに
前回の AWS Lambda のCustom RuntimeでPHP用のLayer作成 で作成したLayerを使用します。
(Stackery社のlayerでも問題ないです。)作業環境
LayerのZip作成にdockerを、AWSへのアップロードにaws-cliを使います。
- pyenv
- 1.2.9-2
- pip
- 9.0.3(python3.6.5)
- aws-cli
- 1.16.96(python3.6.5)
- sam
- 0.14.2
作業はMacOSです。
各種インストールは割愛します。使用ソースコード
https://github.com/Sunochi/LambdaTestFuntion
ログ出力について
AWS LambdaでPHPを使用した際の挙動として、ログ出力が不便という点があります。
API Gatewayを使用する場合はブラウザ上にvar_dump()
で表示すれば良いですが、データとして残すためにもCloudwatch logsへ出来る限り可視性が高い状態で出力しておくことが望まれます。
最近はcloudwatch logsの機能で、json形式のログから検索も可能になっているようなので、ログ出力はちゃんとしようと思う毎日です。
さて、今回はprintf
とvar_dump
とerror_log
の挙動の違い、現時点でのPHPでのログ出力方法のベストプラクティスをまとめておきます。テストコード
index.php<?php function print_log(){ $temp_array = [ 'hoge' => "foo", 'var' => 2, ]; printf("Hello World!\nhogehoge"); printf($temp_array); } function var_dump_log(){ $temp_array = [ 'hoge' => "foo", 'var' => 2, ]; var_dump("Hello World!\nhogehoge"); var_dump($temp_array); } function error_log_log(){ $temp_array = [ 'hoge' => "foo", 'var' => 2, ]; error_log("Hello World!\nhogehoge"); $json_temp_array = json_encode($temp_array); error_log($json_temp_array); } function main(){ print_log(); var_dump_log(); error_log_log(); }結果
error_log()
とjson_encode
の組み合わせが最適です。printf
Lambda上のログ詳細: "Hello World!\nhogehogeArray" CloudWatch logs: Hello World! <行切り替え>hogehogeArrayprintfで表示は可能ですが、複数のログを出力した際に、
1行にまとめて表示される問題があります。
\n
がCloudwatch logsでの行切り替えになっているため、想定通りの出力にならないことがありそうです。var_dump
Lambda上のログ詳細: "string(21) \"Hello World!\nhogehoge\"\narray(2) {\n [\"hoge\"]=>\n string(3) \"foo\"\n [\"var\"]=>\n int(2)\n}\n" CloudWatch logs: string(21) "Hello World! <行切り替え>hogehoge" <行切り替え>array(2) { <行切り替え>["hoge"]=> <行切り替え>string(3) "foo" <行切り替え>["var"]=> <行切り替え>int(2) <行切り替え>}実際に出力してみるとわかりますが、とてもログを参考にできたものはないです。ブラウザ上に表示する場合はvar_dump()がいいと思いますが、ログ出力には向いていないのがわかると思います。
error_log
error_logにArrayを入れるとwarningが出るため、先にjson_encodeしておきます。()
```
Lambda上のログ詳細: ""CloudWatch logs: Hello World!
<行切り替え>hogehoge
<行切り替え>
{
"hoge": "foo",
"var": 2
}
```printf, var_dumpと比べて、行切り替えのタイミングが大分よくなりました。
ただ、\nは行切り替えしたくないので、以下のラッパー関数を作成します。logging.phpfunction logging ($output) { $log = ""; if(is_array($output)){ $log = json_encode($output); $log = str_replace("\n", "\\n", $log); } else { $log = str_replace("\n", "\r", $log); } error_log($log); }結果
Lambda上のログ詳細: "" CloudWatch logs: Hello World! hogehoge <行切り替え> { "hoge": "foo", "var": 2 }いいですね!Lambda上のログ詳細に出したい場合(デバッグ時)は、
var_dumpを使用すればいいと思います。まとめ
PHPのログ出力について、ログ関数を入れてまで本格的にやりたくないので、手軽にできる方法を探しました。
今のところ、error_logのラッパー関数が使い心地がいいですが、よりオススメの方法があれば知りたいです。参考
- 投稿日:2019-02-28T00:48:30+09:00
HomebrewでPHP7.2をインストール
環境
- macOS Siera 10.12.6
インストール済みのPHPバージョン
$ php -v PHP 5.6.30 (cli) (built: Oct 29 2017 20:30:32) Copyright (c) 1997-2016 The PHP Group Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologiesインストール
Formulaを探す。
$ brew search php72 ==> Formulae php@7.2$ brew install php@7.2 ==> Installing dependencies for php@7.2: apr, openssl, apr-util, argon2, aspell, autoconf, brotli, c-ares, libidn, libmetalink, libssh2, jansson, jemalloc, libev, libevent, nghttp2, openldap, rtmpdump, curl-openssl, libtool, unixodbc, freetds, libpng, freetype, gettext, libffi, pcre, glib, gmp, icu4c, jpeg, libpq, libsodium, libzip, readline, sqlite, tidy-html5 and webp ==> Installing php@7.2 dependency: apr ==> Downloading https://homebrew.bintray.com/bottles/apr-1.6.5.sierra.bottle.tar ######################################################################## 100.0% ==> Pouring apr-1.6.5.sierra.bottle.tar.gz ==> Caveats略
確認
$ php -v PHP 5.6.30 (cli) (built: Oct 29 2017 20:30:32) Copyright (c) 1997-2016 The PHP Group Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologiesターミナルを再起動後、改めて確認するも変化なし。
シンボリックリンクの作成
$ brew link php72 --force Linking /usr/local/Cellar/php@7.2/7.2.15... 25 symlinks created If you need to have this software first in your PATH instead consider running: echo 'export PATH="/usr/local/opt/php@7.2/bin:$PATH"' >> ~/.bash_profile echo 'export PATH="/usr/local/opt/php@7.2/sbin:$PATH"' >> ~/.bash_profileパスを通す
$ echo 'export PATH="/usr/local/opt/php@7.2/bin:$PATH"' >> ~/.bash_profile $ echo 'export PATH="/usr/local/opt/php@7.2/sbin:$PATH"' >> ~/.bash_profile再び確認
ターミナルを再起動後、確認。
$ php -v PHP 7.2.15 (cli) (built: Feb 26 2019 10:47:19) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.2.15, Copyright (c) 1999-2018, by Zend Technologiesおしまい。