- 投稿日:2021-06-03T23:03:28+09:00
[Laravel6.x]SameSite属性の設定
はじめに この記事はプログラミング初学者による備忘録用の記事であり、また、少しでも他の初学者のお役に立てればと思い書いています。 今回は、Chromeの検証機能にてIndicate whether to send a cookie in a cross-site request by specifying its SameSite attributeというIssuesが発生しましたので、解決策及びIsuuesに関する情報を記録しておきたいと思います。 間違いなどがございましたら、ご指摘のほどよろしくお願い致します。 Issuesの内容 Indicate whether to send a cookie in a cross-site request by specifying its SameSite attribute どうやら、クッキーのSameSite属性が設定されていないか無効であるため、設定してくれと警告されているようです。 SameSite属性を設定することで、クロスサイトのコンテキストでクッキーが設定されることを防ぎ、ユーザーデータが誤って第三者に漏れたり、クロスサイトリクエストフォージェリが発生したりするのを防ぐことができるとのこと。 さらにメッセージを読むと、 ・クッキーがクロスサイト・コンテキストに設定されることを意図している場合は、SameSite=None or Secureを指定(Secure属性を使用できるのはHTTPS経由で送信されるCookieのみ) ・クロスサイト・リクエストでクッキーを設定しない場合は、SameSite=StrictまたはSameSite=Laxを指定 上記のようなルールに従ってCookieの属性を更新することで、Issuesは解決できるようです。 SameSite属性とは SameSite 属性により、サーバーがオリジン間リクエスト (ここでサイトは登録可能なドメインによって定義されます) と一緒にクッキーを送るべきではないことを要求することができます。これは、クロスサイトリクエストフォージェリ攻撃 (CSRF) に対していくらかの防御となります。 SameSite属性には、ユーザーが意図しない形でのCookieの送信を発生させないようにする特徴があると言えます。 引用:MDN HTTP Cookie の使用 (Cookie の送信先の定義) 使用例 例えば、https://example.com というページに、https://www.google.com/ へのリンクがあり、以前 https://www.google.com/ にアクセス済みでクッキーを受け取っていた場合、再度リンク先に送信するリクエストデータの中に、以前に受け取った期限切れではないクッキーデータが存在する場合登録されます。この動作はCSRFなどの脆弱性を生む原因になるので、その対策としてSameSite属性が存在しています。 重要なこと セキュリティやプライバシーの観点で重要なことは、cross-siteからのリクエストであり、そのリクエストに Cookie を含めるかどうかを決めるのがSameSite属性の役割であるといえます。 SameSite属性に指定できる値 指定できる値は Strict, Lax, None の3つです。 指定する値によって、Cookieの扱い方が異なるので下記の表にまとめておきます。 SameSite属性の値 意味 None 全てのcross-siteなリクエストに対して Cookie が付与されます。つまり、Noneを使うことで、意図的に第三者のコンテキストでクッキーを送信してほしいことを明確に伝えることができます。例えば、埋め込みコンテンツ、アフィリエイトプログラム、広告、複数のサイトへのサインインなど、他のサイトが利用するサービスを提供している場合は、Noneを使用して意図を明確にする必要があります。 Strict 他のドメインへのリクエストを送る際、クッキーはセットされず、same-siteに対するリクエストにのみCookieが付与されます。つまり、クッキーはファーストパーティのコンテキストでのみ送信されます。例えば、Aサイトでログインしている状態で、Bサイト上にあるAサイトへのリンクをクリックした場合、リクエストにCookieが付与されないため未ログイン状態になり、再度ログイン処理や再読み込みが必要となります。 Lax トップレベルのナビゲーションで、尚且つメソッドがsafeであれば、他のドメインへのリクエストであってもクッキーをセットします。POSTメソッドを使ったフォームのような、HTTPメソッドによるcross-siteなトップレベルのナビゲーションに対してはCookieが付与されません。従って、POSTメソッドなどを使ったフォームのサブミットに対するCSRF攻撃の対策として有効です。 注意: StrictもLaxも、サイトのセキュリティを完全に解決するものではありません。クッキーはユーザーのリクエストの一部として送信されるので、他のユーザー入力と同じように扱う必要があります。つまり、入力をサニタイズし、検証するということです。サーバー側のパスワード等の漏洩厳禁なデータを保存するためにクッキーを使用してはいけません。 LaravelでSameSite属性の設定方法 config/session.phpの'same_site' => null,を変更する。 config/session.php /* |-------------------------------------------------------------------------- | Same-Site Cookies |-------------------------------------------------------------------------- | | This option determines how your cookies behave when cross-site requests | take place, and can be used to mitigate CSRF attacks. By default, we | do not enable this as other CSRF protection services are in place. | | Supported: "lax", "strict", "none" | */ 'same_site' => null, 補足 ・safeなメソッドとは RFC7231 4.2.1 Safe Methodsによると、 ・GET ・HEAD ・POTIONS ・TRACE の4つをsafeなメソッドとしており、POSTやDELETEより比較的に安全といえる。 ・same-site と cross-siteの違いとは 簡単にいうと、ドメインが同じであればsame-siteであり、ドメインが異なればcross-siteです。 詳しい違いは下記リンク先を参考にしてください。とてもわかりやすい記事でした。 web.dev [Understanding "same-site" and "same-origin"] ・トップレベルナビゲーションとは アドレスバーに表示されているURLのオリジンドメインをトップレベルナビゲーションとして、オリジンドメインAからオリジンドメインBに移るような画面遷移の場合、CSRFの対象にならないので安全であると判断してクッキーの付加が許可されているのだと思われます。 例: http://sample.com/ にあるリンクを踏んでhttp://another.com/ に遷移した場合、アドレスバーにはhttp://another.com/ が表示されるので、これはトップレベルナビゲーションであるといえます。 おわりに 米Googleは2020年4月に新型コロナウイルスの影響を受け、Google Chrome 80からの SameSite Cookieの仕様変更を一時的にロールバックすると発表がありました。 新型コロナウイルスが収まり次第、また発表がありそうですね。 参考文献 MDN HTTP Cookie の使用 (Cookie の送信先の定義) web.dev [SameSite cookies explained] web.dev [Understanding "same-site" and "same-origin"] draft-ietf-httpbis-rfc6265bis-06#section-5.2 rfc7231 - IETF Tools SameSite 属性を使った Cookie のセキュアな運用を考える
- 投稿日:2021-06-03T22:58:31+09:00
Laravelのコマンド
Laravelのコマンド Laravel のプロジェクトを作成 composer create-project laravel/laravel [プロジェクト名] --prefer-dist 6.* composer create-project laravel/laravel laravel_bbs --prefer-dist 6.* データベースの作成 データベースにログイン mysql -u root -p データベースの作成 CREATE DATABASE laravel_bbs_development; 作成したデータベースの確認 SHOW DATABASES; SQL の入力画面から抜ける exit; Laravel の開発用サーバを起動 cd laravel_bbs php artisan serve --port=$PORT コマンド ls pwd cd mv cp
- 投稿日:2021-06-03T22:30:35+09:00
Laravelの知識チェック
Laravelの知識チェック appディレクトリ /laravel_sample/app アプリケーション作成のコアとなるファイルを配置するディレクトリ。コントローラやモデルのファイルはこのディレクトリ内に配置する。 bootstrap ディレクトリ index.phpを読み込み、最初に呼び出される、各種起動ファイルが配置されたディレクトリです。直接編集する機会は少ない。 config ディレクトリ configuration(設定)の略。このディレクトリには、アプリケーションの設定に関する各種ファイルが保存されている。 app.phpファイル アプリケーションの設定ファイル TrustProxies.phpファイル app/Http/MiddleWare/TrustProxies.php AWS上で正しく動作させるファイル database ディレクトリ データベースのマイグレーション(DB設定)ファイルと、初期データの登録を行うためのファイル(シードファイル)などを配置するディレクトリです。 public ディレクトリ Laravelアプリケーションのドキュメントルート(公開ディレクトリ)となるディレクトリです。最初に読み込まれる index.php の他、画像、JavaScript、CSSファイルなども配置されます。 resources ディレクトリ ビューに関するファイルや、JavaScript、CSSの元となるファイル、言語設定に関するファイルなどが配置されるディレクトリです。 routes ディレクトリ ルーティングに関するファイルを配置するディレクトリです。今回のテキストではその中でもweb.phpを頻繁に編集することになります。 storage ディレクトリ 各種データの保管を行う。 アプリケーションにより使用されるファイルを保存 storage/app ディレクトリ、フレームワークが生成するファイル等を保存。 storage/framework ディレクトリ、 アプリのログが保存される storage/logs ディレクトリなどが含まれる。 tests ディレクトリ 自動テストに関するコードを配置するディレクトリです。今回のテキストでは自動テストはとりあげません。 vendor(ベンダー) ディレクトリ composerでインストールしたパッケージが配置されるディレクトリです。 Laravelのコアとなる仕組みに関するファイルなどもこのディレクトリに含まれる。 .envファイル laravel の環境設定ファイルとなる隠しファイル DB_CONNECTION=mysql から始まる行をさがす DB_DATABASE と DB_PASSWORD の値を編集する Laravelで最初に読み込まれるファイルはどのファイルか? ・index.phpファイル 2. ビューが生成するのは何か? ・HTMLファイル 3.ルーティングに関するファイルが配置されるディレクトリはどのディレクトリか? ・routes ディレクトリ 3. コントローラやモデルのファイルを配置するのはどのディレクトリか? ・appディレクトリ 4. Laravelのドキュメントルートとなるのはどのディレクトリか? ・public ディレクトリ
- 投稿日:2021-06-03T22:30:35+09:00
Laravelのディレクトリ・ファイル
Laravelのディレクトリ・ファイル routes ディレクトリ ルーティングに関するファイルを配置するディレクトリです。今回のテキストではその中でもweb.phpを頻繁に編集することになります。 web.phpファイル 【/アプリ名/routes/web.php】 ルーティングファイル。 URLの後半部分(ドメイン名より後ろの部分) リクエストメソッド の組み合わせによって、どの処理が行われるかを決める仕組み resources ディレクトリ ビューに関するファイルや、JavaScript、CSSの元となるファイル、言語設定に関するファイルなどが配置されるディレクトリです。 welcome.blade.phpファイル 【/アプリ名/resources/views/welcome.blade.php】 一番最初にアクセスした時の Laravel のスタートページ appディレクトリ 【/アプリ名/app】 アプリケーション作成のコアとなるファイルを配置するディレクトリ。コントローラやモデルのファイルはこのディレクトリ内に配置する。 bootstrap ディレクトリ index.phpを読み込み、最初に呼び出される、各種起動ファイルが配置されたディレクトリです。直接編集する機会は少ない。 config ディレクトリ configuration(設定)の略。このディレクトリには、アプリケーションの設定に関する各種ファイルが保存されている。 app.phpファイル アプリケーションの設定ファイル TrustProxies.phpファイル app/Http/MiddleWare/TrustProxies.php AWS上で正しく動作させるファイル database ディレクトリ データベースのマイグレーション(DB設定)ファイルと、初期データの登録を行うためのファイル(シードファイル)などを配置するディレクトリです。 public ディレクトリ Laravelアプリケーションのドキュメントルート(公開ディレクトリ)となるディレクトリです。最初に読み込まれる index.php の他、画像、JavaScript、CSSファイルなども配置されます。 storage ディレクトリ 各種データの保管を行う。 アプリケーションにより使用されるファイルを保存 storage/app ディレクトリ、フレームワークが生成するファイル等を保存。 storage/framework ディレクトリ、 アプリのログが保存される storage/logs ディレクトリなどが含まれる。 tests ディレクトリ 自動テストに関するコードを配置するディレクトリです。今回のテキストでは自動テストはとりあげません。 vendor(ベンダー) ディレクトリ composerでインストールしたパッケージが配置されるディレクトリです。 Laravelのコアとなる仕組みに関するファイルなどもこのディレクトリに含まれる。 .envファイル laravel の環境設定ファイルとなる隠しファイル DB_CONNECTION=mysql から始まる行をさがす DB_DATABASE と DB_PASSWORD の値を編集する - - 1. Laravelで最初に読み込まれるファイルはどのファイルか? ・index.phpファイル 2. ビューが生成するのは何か? ・HTMLファイル 3.ルーティングに関するファイルが配置されるディレクトリはどのディレクトリか? ・routes ディレクトリ 3. コントローラやモデルのファイルを配置するのはどのディレクトリか? ・appディレクトリ 4. Laravelのドキュメントルートとなるのはどのディレクトリか? ・public ディレクトリ
- 投稿日:2021-06-03T18:51:29+09:00
laravelの備忘録
自分が忘れた時に備えて書いておく記事です。 公式から必要な事だけを引っ張ってきた事。 環境 2010/06/02の時 OS:Windows8.1 laravel:8.44.0 ・サーバー起動 php artisan serve ルートディレクトリでこれを叩く ・サーバーの停止 ctrl+C アプリケーションでも使える ・バージョン確認 php artisan -version または php artisan -v こちらでもバージョンを確認できるが、オプションも表示される ・マイグレーションの作成 データベースにデータの保存をするテーブルの型作成 php artisan migrate:make [任意のテーブル名] ・マイグレーションの実行 php artisan migrate ※ミスるとテーブルが作られたままになるのでテーブルを削除するか他の方法で元に戻す ※公式ではロールバック方法が掛かれているがまだ試してない ロールバックコマンド migrate:rollback ・メンテナンスモード メンテナンスにする php artisan down メンテナンスを解除する php artisan up ※めんてて書いておく記事です。 環境 2010/06/02の時 OS:Windows8.1 laravel:8.44.0 ・サーバー起動 php artisan serve ルートディレクトリでこれを叩く ・サーバーの停止 ctrl+C アプリケーションでも使える ・バージョン確認 php artisan -version または php artisan -v こちらでもバージョンを確認できるが、オプションも表示される ・マイグレーションの作成 データベースにデータの保存をするテーブルの型作成 php artisan migrate:make [任意のテーブル名] ・マイグレーションの実行 php artisan migrate ※ミスるとテーブルが作られたままになるのでテーブルを削除するか他の方法で元に戻す ※公式ではロールバック方法が掛かれているがまだ試してない ロールバックコマンド migrate:rollback ・メンテナンスモード php artisan down メンテナンスを解除する php artisan up ※メンテナンスモードしてると503にされるからコードを間違えたかと勘違いするので注意!!(気づかないで何時間もコードとにらめっこしてしまった・・・) 公式サイト:http://laravel.jp/
- 投稿日:2021-06-03T14:10:05+09:00
Sequel ProでMySQL8.0以降を使用して、DockerからアプリケーションからMySQLをつなげるときにはまったこと
問題発生 Dockerを使ってアプリケーション(Laravelなど)からMySQLに接続する(php artisan migrateなどで)時に、 接続できません!! とターミナルで怒られてしまったのですが、 Sequel Proを見ても動いているし、 MySQLのコンテナに入っても動いているので原因がわからず、 envファイルが違うのかなんなと原因究明していて迷走していたので記事にしました。。。 結論 MySQLの認証方式が「mysql_native_password」ではなく、なぜか「caching_sha2_password」を使う必要があったため。 ただ、Sequel Proでは「caching_sha2_password」使えないから「mysql_native_password」を使えよと怒られたので、 使ってみたんですが、この場合はアプリケーションとMySQLは接続できたのでSequel Proを動かしてみたら以下のように怒られてしまいました。 →Sequel Proで使うために「mysql_native_password」を使用したからこんなことになってしまったのです。 多分MySQL8.0以降を使うならMySQL Workbenchとか別のもの使った方がいいのか? Sequel Pro Nightly build より MySQL said: Authentication plugin 'caching_sha2_password' cannot be loaded: dlopen(/usr/local/mysql/lib/plugin/caching_sha2_password.so, 2): image not found まとめ MySQL8.0.4以降 のログイン認証方式は caching_sha2_password がデフォルト PHP 7.1.16, 7.2.4 以降のバージョンから caching_sha2_password に対応しているので「caching_sha2_password」をそのまま利用する しかし、Sequel Pro Nightly buildでは「caching_sha2_password」を使用できない 以上、Sequel ProとMySQL8.0以降を使用する機会があったらご注意ください。(Sequel Pro不具合多いし、MySQL Workbench使った方がいいのかな...) ほんと環境設定とかで止まるのが辛いです。 参考文献 https://www.suzu6.net/posts/256-mysql8-pdomysql-caching_sha2_password/ https://qiita.com/ucan-lab/items/3ae911b7e13287a5b917 https://qiita.com/ucan-lab/items/b68db1db931c954da921
- 投稿日:2021-06-03T13:43:03+09:00
NextjsでGMOペイメントゲートウェイのカードを登録する処理(プロトコルタイプ)
対象読者 NextやReactでcdnを使いたい方 GMOペイメントゲートウェイをNextやReactで使いたい方 1の人はまとめまで飛んで下さい バックグラウンド 業務で新サービスを作っています。 バックエンドはLaravel、フロントエンドはNextjsを使用しています。 その新サービスでは決済はクレジットカードを利用しています。 決済に関しては個人的にはstripeが良かったのですが、費用とかを鑑みた末... 選ばれたのはGMOでした また仕様によりプロトコルタイプを選択しました。 stripe等のサービスと違いSDKもなくライブラリも充実しているわけでは無いのでなかなかに実装に工夫が必要でした。 値段とサービスどちらを取るかという論点は人によって価値観が違うので一概にどちらが優れているという比較はできませんが、これだけは言わせてください。 ドキュメントに関して一言だけ文句を言わせてください。このドキュメントの多さと分かりにくさ、「あえて言おう、カスであると」 嘘です。GMOさんGMOの皆さんごめんなさい このセリフ一度でいいから言ってみたかっただけなんです GMOの良さはstripeやpaypalより手数料が安いところです。 あと電話サポートの方の対応は丁寧かつ、技術の知識もあるためか正確な返答が得られた点がとても良かったです。 ドキュメントで躓いたら片っ端から電話しましょう! ただ公平な目で判断した際に少し手間であるのは紛れもない事実です。これからのユーザーやGMOさんのためにもなると思ったのでこの記事を書くことにしました。強いられてるんだQiitaを書くことを 前提条件 カードから毎月反響した分だけ引き下ろすためにはカードの持ち主をGMOに登録しておくことと、カード自体を登録する必要があります。 GMOのAPIからの返り値をarrayにする汎用関数 不満点その2。返り値が不親切であること そのため使いやすく整形して返す関数を定義しました。 まずgmoのAPIから帰ってくる生のbodyがこちらです。 "CardSeq=0&DefaultFlag=1&CardName=&CardNo=*************111&Expire=2405&HolderName=&DeleteFlag=0" ええ、、、&つなぎの文字列、、、 「あえて言おう、(自重) このままだと使いにくいんで配列に変換する汎用関数を作ります。 curlやguzzleで取ってきた値を$rawReturnと仮定します public static function gmoFormatter(string $url, array $params): array { $rawReturn = self::postCurl($url, $params); // 実際にpostする汎用関数(長くなるので中身は省略) curlでもguzzleでも好きなものを使ってください $status = $rawRet['status']; $dataList = explode('&', $rawReturn['data']); // & 分解 foreach($dataList as $d) { $dkv = explode('=', $data); // = 分解: 左辺がkey, 右辺がvalueになっている $ret[$dkv[0]] = $dkv[1]; $dkv[0] === 'ErrCode' && $status === 200 && $status = 400; // 通信が成功しつつエラーコードがあればstatusを400にしちゃいます } $ret = ['status' => $status, 'data' => $ret]; // エラーコードがあったときにはstatusを書き換えているので判定がしやすくなっています。 return $ret; } 以下が整形した結果の配列です。どうでしょう? 圧倒的じゃないか、我が軍は { "stauts":200, "data":[ { "CardSeq":"0", "DefaultFlag":"1", "CardName":"", "CardNo":"*************111", "Expire":"2405", "HolderName":"", "DeleteFlag":"0" } ] } あらかじめ会員登録をしておいてメンバーIDを作っておく 毎月決済するための加盟店のカードを登録するためには加盟店をGMOのショップの会員に登録した上で、その会員のカードがどれかわからなければなりません。 そこで、今回はサービスの会員登録時に発行される一意のユーザーIDを利用して、memberIDを作成します。 そしてそのmemberIDと加盟店の名前を元にGMOのショップの会員としても同時に登録する処理を行なっています。 一言で言うと、ユーザーのinsertと並列してGMO側にも必要な情報を投げて会員登録している形になります。 use App\Domains\GmoCurlDomain; class GmoDomain { private function __constructor() { // gmo env系 $this->apiUrl = config('custom.gmoApiUrl'); $this->siteId = config('custom.gmoSiteId'); $this->sitePw = config('custom.gmoSitePw'); $this->shopId = config('custom.gmoShopId'); $this->shopPw = config('custom.gmoShopPw'); } /** * 会員登録する * @param String $memberName * @param String $memberId * @return Array $exe */ public function saveMember(string $memberName, string $memberId): array { $url = $this->apiUrl . '/' . 'payment/' . 'SaveMember.idPass'; $params = [ 'SiteID' => $this->siteId, 'SitePass' => $this->sitePw, 'MemberID' => $memberId, 'MemberName' => mb_convert_encoding($memberName, 'SJIS', 'UTF-8'), // 文字化け用に mb_convert_encoding ]; $exe = GmoCurlDomain::gmoFormatter($url, $params); return $exe; } } プロトコルタイプってなに? トークン決済ってなに? カード登録や決済の際のお話です。 加盟店のサーバーサイド等を経由して入力されたカード番号を直接GMOのカード登録や決済が行われるとカード番号の流出等のリスクが上がってしまうので、GMOではこのトークン決済というものを利用しています。 プロトコルタイプでカード登録をする方法2選 入力されたカード番号から決済やカード登録に必要なトークンを取得するために以下の2つの方法があります。 それぞれエンドポイントは違います。 そのままAPIに投げる (sslのみ有効) cdnを利用してAPIに投げる (sslじゃなくてもOK) cdnを利用するとPOSTする前に暗号化してPOSTしてくれます。これでsslの代わりを担って通信を安全にしてくれています。 今回私は、ローカルでもテストとして使用したかったので2を選択しました。 Nextjs(React)でクレカを登録する 前提条件 GMOから受け取ったtokenを使ってクレカを登録する際には、以下の様な処理を行うAPIにポストしています。 もらってきたtokenと顧客固有のid(saveMemberのときにポストしたものと同じもの)やサイトID等を投げます。 カードの番号はもちろん、セキュリティコード、有効期限等が含まれていないのがちゃんとわかります。 public function saveNewCard( string $memberId, string $token ): array { $url = $this->apiUrl . '/' . 'payment/' . 'SaveCard.idPass'; $params = [ 'SiteID' => $this->siteId, 'SitePass' => $this->sitePw, 'MemberID' => $memberId, 'Token' => $token, 'DefaultFlag' => 1, ]; $exe = GmoCurlDomain::gmoFormatter($url, $params); return $exe; } Nextjs(React)でcdn内のclassをインスタンス化 Nextjsでcdnのクラスをインスタンス化して使う?できらぁ! cdnで定義されたクラスをインスタンス化する際に、いかにそのクラスをNext(React)上に記述しないかが重要なポイントです。 以下のプロセスはそれを念頭に置いた上で行いました。 Multipayment がそのアンタッチャブルなクラスに該当します。 失敗例1 (devだと動くけどcompileはできない例) dangerouslySetInnerHTMLを使用しています。 getCreateCardTokenはdangerouslySetInnerHTMLで定義したつもりですが、それはブラウザ上で定義されているだけで、next(react)内では定義してないのもちろんコンパイルはできません。でnpm run devでは動きますが、、、 const Credit: NextPage = () => { const [mess, setMess] = useState<string[]>([]); const startCreateCard = async (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); setMess(['送信中']); const ret = await getCreateCardToken(e); if (ret && ret['resultCode'] === '000') { // カード情報のトークン化に成功すればresultCode 000が返ってきます const res: Response = await fetchCreateCredit(newToken); // 返ってきたtokenをバックエンドにPOST // バックエンドでは受け取ったトークンやユーザー情報を利用してGMOのカード作成エンドポイントにポストする switch (res.status) { case 200: setMess(null); router.push(`<next page>`) break; default: setMess([`エラーが発生しました。`]); } } else { setMess([`エラーが発生しました。`]); } }; return ( <div> <Head> <script defer src={`${GMO_TOKEN_URL}`}></script>{/* cdnのurlです */} <script defer dangerouslySetInnerHTML={{ __html: ` // 定義されたままになるので再定義可能なvarを使う!! // 即読み込みできないのでタイムアウト setTimeout(() => { Multipayment.init('<shopID>'); }, 1000); // componentの移動の際にも定義されたままになるので再定義可能なvarを使う!! var getCreateCardToken = (e) => { return new Promise(function (resolve, reject) { Multipayment.getToken( { cardno: e.target.card.value, expire: e.target.expire.value, securitycode: e.target.securitycode.value, }, resolve ); }); } `, }} /> </Head> <form onSubmit={startCreateCard}> <label>カード番号</label> <input type="text" name="card" required maxLength={16} minLength={16} /> 以下省略 </form> </div> ) } export default Credit; 失敗例2 (compileできるけど動かない例) では、適当にgetCreateCardTokenを宣言だけしてみたらどうだろう? 上のコードに以下を追加してみます。 useEffect(() => { var getCreateCardToken: Function; }, []); ページがマウントされた後にgetCreateCardTokenを宣言、dangerouslySetInnerHTMLでgetCreateCardTokenを上書きする作戦です。 "getCreateCardToken" is not a function これならとりあえずはコンパイルできる状態にはなりました。 しかし、今度はブラウザ上で動かない。 dangerouslySetInnerHTMLで上書きするとgetCreateCardTokenはundefinedとなってしまっていました。 この後も色々と試行錯誤したが、長くなるので割愛します。 正解 コンパイルできて、ちゃんと動く正解の形です。 文字列を関数に変換できればいいのではないか?と思い、このような実装をしました。 予想通り完璧に動きました。 const Credit: NextPage = () => { const [mess, setMess] = useState<string[]>([]); const strFunction = ` Multipayment.init('${GMO_KEY}'); return new Promise(function (resolve, reject) { Multipayment.getToken( { cardno: e.target.card.value, expire: e.target.expire.value, securitycode: e.target.securitycode.value, }, resolve ); }); `; let getCreateCardToken = new Function('e', strFunction); useEffect(() => { getCreateCardToken = new Function('e', setFunction); }, []); const startCreateCard = async (e: React.FormEventHandler<HTMLFormElement>) => { e.preventDefault(); setMess(['送信中']); const ret = await getCreateCardToken(e); if (ret && ret['resultCode'] === '000') { // カード情報のトークン化に成功すればresultCode 000が返ってきます const res: Response = await fetchCreateCredit(newToken); // 返ってきたtokenをバックエンドにPOST // バックエンドでは受け取ったトークンやユーザー情報を利用してGMOのカード作成エンドポイントにポストする switch (res.status) { case 200: setMess(null); router.push(`<next page>`) break; default: setMess([`エラーが発生しました。`]); } } else { setMess([`エラーが発生しました。`]); } }; return ( <div> <Head> <script defer src={`${GMO_TOKEN_URL}`}></script> </Head> <form onSubmit={startCreateCard}> <label>カード番号</label> <input type="text" name="card" required maxLength={16} minLength={16} /> 以下省略 </form> </div> ) } export default Credit; ミソはこの部分です。 const strFunction = ` Multipayment.init('${GMO_KEY}'); return new Promise(function (resolve, reject) { Multipayment.getToken( { cardno: e.target.card.value, expire: e.target.expire.value, securitycode: e.target.securitycode.value, }, resolve ); }); `; let getCreateCardToken = new Function('e', strFunction); // cdnがロードされていないので ここは意味合い的には宣言だけ useEffect(() => { getCreateCardToken = new Function('e', strFunction); // 上書きする ここで定義される。 cdnがロードされた後に実行される }, []); useStateでなく、letを使ったのは一回だけしか変更がないためletでいいかな〜って思ったからです。 let getCreateCardToken = new Function('e', strFunction) では以下のような処理が行われています。 let getCreateCardToken = (e) => { // strFunctionの中身 Multipayment.init('${GMO_KEY}'); return new Promise(function (resolve, reject) { Multipayment.getToken( { cardno: e.target.card.value, expire: e.target.expire.value, securitycode: e.target.securitycode.value, }, resolve ); }); } まとめ nextやreactでcdn内のクラスをインスタンス化して使いたい場合、以下のようにやると良いです。 import Head from 'next/head'; const Sample: NextPage = () => { const strFunction = ` <SomeClass>.init(); 何らかの処理 `; let cdnFunction = new Function('arg', strFunction); useEffect(() => { cdnFunction = new Function('arg', strFunction); }, []); return ( <div> <Head> <script src={`${CDN_SOURCE}`}></script> </Head> </div> ) } export default Sample; GMOペイメントゲートウェイについて、手数料が安く電話サポートが充実している点はとても素晴らしいと思います。 少し高くても手間をかけたくなかったらstripe、手間をかけてでも手数料を節約したいならGMOペイメントゲートウェイを選ぶといいんではないでしょうか。 最後に一言 すまんが、みんなのLGTMをくれ
- 投稿日:2021-06-03T10:26:47+09:00
【heroku】デプロイ後の更新を反映させる
herokuにて既にデプロイしているアプリを更新した際に、更新を反映させる手順を残しておきます。 1. 変更したファイルをステージングに追加する git add . //変更があったすべてのファイルをステージングに追加 git add ファイル名 //指定したファイルをステージングに追加 2. ステージングに追加した変更をコミットする git commit -m "コメント" 3. コミットした変更をherokuにpushする git push heroku master 4. migrationファイルにも変更があれば heroku run php artisan migrate:rollback heroku run php artisan migrate 【参考サイト】 https://entsukablog.com/laravel-heroku-deploy/#i
- 投稿日:2021-06-03T04:09:25+09:00
LaravelでYamlファイルを簡単に扱う方法
はじめに 何かといろいろな活用法があるYamlファイル。この記事では、Laravelで指定ディレクトリ以下のYamlファイルを読み取り、配列に変換したうえで取得する方法について解説します。 使用ライブラリ symfony/yaml 動作確認環境 PHP-8.0 Laravel-8.0 インストール 以下は開発環境でのみYamlファイルを使用する場合のインストール方法です。 本番環境でも使用する想定がある場合は--devを外して下さい。 composer require --dev symfony/yaml サンプルコード 下記クラスをコンストラクトインジェクションする。またはクラスをmakeして下さい。 なお、ネームスペースは適時変更下さい。 Reader.php <?php namespace Hogehoge; use File; use Illuminate\Filesystem\Filesystem; use LogicException; use Symfony\Component\Yaml\Yaml; /** * Class Reader */ class Reader { /** * @var \Symfony\Component\Yaml\Yaml */ protected $yaml; /** * @var \Illuminate\Filesystem\Filesystem */ protected $file; /** * YamlFileOperation constructor. */ public function __construct( Filesystem $file, Yaml $yaml ) { $this->yaml = $yaml; $this->file = $file; } /** * Read yaml files. * * @param string $directoryPath * @param array $exceptFileNames * @return array */ public function readFileByDirectoryPath(string $directoryPath, array $exceptFileNames = []): array { $yamlFiles = $this->readByDirectoryPath($directoryPath); // Exclude from creation if (! empty($exceptFileNames) && ! empty($yamlFiles)) { $yamlFiles = collect($yamlFiles)->filter(function ($value, $key) use ($exceptFileNames) { return ! in_array(basename($key, '.yml'), $exceptFileNames, false); })->all(); } return $yamlFiles; } /** * Read yaml files. * * @param string $directoryPath * @param string $findFileName * @return array */ public function readFileByFileName(string $directoryPath, string $findFileName): array { $yamlFiles = $this->readFileByDirectoryPath($directoryPath); foreach ($yamlFiles as $fileName => $yamlFile) { if (basename($fileName, '.yml') === $findFileName) { return $yamlFile; } } return []; } /** * Reading definition data. * * @param string $targetDirectoryPath * @return array */ protected function readByDirectoryPath(string $targetDirectoryPath): array { if (! $this->file->isDirectory($targetDirectoryPath)) { throw new LogicException($targetDirectoryPath.': read path must be a directory'); } $filePaths = $this->getAllFilePath($targetDirectoryPath); return $this->parseAllYaml($filePaths); } /** * Recursively get a list of file paths from a directory. * * @param string $directoryPath * @return array */ protected function getAllFilePath(string $directoryPath): array { $filePaths = []; if (! $this->file->isDirectory($directoryPath)) { throw new LogicException('Not a Directory'); } $files = $this->file->allFiles($directoryPath); foreach ($files as $file) { $realPath = (string) $file->getRealPath(); $filePaths[$realPath] = $realPath; } return $filePaths; } /** * Parse all definition Yaml files. * * @param array $filePaths * @return mixed */ protected function parseAllYaml(array $filePaths): array { $yamlParseTexts = []; foreach ($filePaths as $filePath) { if (count($this->parseYaml($filePath)) >= 2) { throw new LogicException('Yaml data must be one data per file filePath: '.$filePath); } // Rule that there is always one data in Yaml data $yamlParseTexts[$filePath] = collect($this->parseYaml($filePath))->first(); } return $yamlParseTexts; } /** * Parse Yaml files. * * @param string $filePath * @return mixed */ protected function parseYaml(string $filePath) { $extension = $this->file->extension($filePath); if ($extension !== 'yml') { throw new LogicException('Could not parse because it is not Yaml data filePath: '.$filePath); } return $this->yaml::parse(file_get_contents($filePath)); } } 使い方 Reader.php $yamlReader = app()->make(Reader::class); $result = $yamlReader->readFileByDirectoryPath('yamlが保存されているディレクトリパス', ['除外したいファイル名']); $result2 = $yamlReader->readFileByFileName('yamlが保存されているディレクトリパス', '読み込みたいファイル名'); 関連