- 投稿日:2019-05-21T22:45:02+09:00
privateとprotectedメソッドをPHPUnitでテストする方法
JavaのJUnitでprivateメソッドをテストする方法はこちら
- 環境 : PHP7.3
ReflectionClassを使ったprivateとprotectedメソッドを実行するメソッドを用意すると便利にテストできる
便利ポイント
- パラメータには、「実行するメソッド名」とその「メソッドの引数」を指定できるようにする。
- privateもprotectedも同じやり方でできるのでメソッド名と引数を指定すれば実行できる
- パラメータで
array $param
とすることでメソッドの引数が1つでも複数でも指定できるようになる
- メソッドの引数が3つの場合(
private function A($a, $b, $c)
)は、doMethod('A', [$a, $b, $c]);
となる- メソッドの引数が1つの場合(
private function A($a)
)は、doMethod('A', [$a]);
となる- メソッドにしておけばprivateとprotectedのメソッドをいくつあっても簡単。な気がする。
privateとprotectedメソッドを実行するメソッド/** * privateメソッドを実行する. * @param string $methodName privateメソッドの名前 * @param array $param privateメソッドに渡す引数 * @return mixed 実行結果 * @throws \ReflectionException 引数のクラスがない場合に発生. */ private function doMethod(string $methodName, array $param) { // テスト対象のクラスをnewする. $controller = new FormController(); // ReflectionClassをテスト対象のクラスをもとに作る. $reflection = new \ReflectionClass($controller); // メソッドを取得する. $method = $reflection->getMethod($methodName); // アクセス許可をする. $method->setAccessible(true); // メソッドを実行して返却値をそのまま返す. return $method->invokeArgs($controller, $param); }例えばこんな感じ
FormController.php(テストしたいクラス)<?php namespace App\Http\Controllers; class FormController extends Controller { public function __invoke() { $param = array(); $param['data'] = $this->ceateListData(); return view('form', $param); } private function ceateListData(): array { $data = [ // 内容はどうでも良いので省略 ]; return $this->addId($data); } protected function addId(array $data): array { if (count($data) !== 0) { // 内容はどうでも良いので省略 } return $data; } }FormControllerTest.php(テストクラス)<?php namespace App\Http\Controllers; use PHPUnit\Framework\TestCase; class FormControllerTest extends TestCase { /** @test */ public function privateメソッドをテストする() { // privateメソッドを実行して結果を取得する. $list = $this->doMethod('ceateListData', []); $actual = gettype($list); $this->assertEquals('array', $actual); } /** @test */ public function protectedメソッドをテストする() { $data = [(object)['data' => 'sample1'], (object)['data' => 'sample2']]; // protectedメソッドを実行して結果を取得する. $list = $this->doMethod('addId', [$data]); foreach ($list as $v) { $this->assertIsInt($v->id); } } private function doMethod(string $methodName, array $param) { // 内容は先頭と同じ } }
- 投稿日:2019-05-21T22:30:35+09:00
AWS Cloud9でLaravelの開発環境構築
はじめに
AWS Cloud9でLaravelの開発環境を構築する手順です。
Cloud9の起動
ブラウザでCloud9を起動します。
コンソールの表示
コンソールが表示されていない場合は、F6キーを押下してコンソールを表示します。
コンソールとは、以下のような「bash」とタブに表示されている場所です。
コンソールでは、コマンドでサーバーを操作することが出来ます。
コマンドの実行方法
コマンドの実行方法がわからない場合は、以下の動画を参考にしてください。
PHP7.3のインストール
2019/05/19時点では、EC2のAmazon LinuxにインストールされているPHPのバージョンは5.6.40であるため、PHP7.3にアップデートします。
以下のコマンドを実行します。
sudo amazon-linux-extras install php7.3以下の表示となったら、「y」を入力し、Enterキーを押下します。
以下のように表示されれば完了。
composerのインストール
composerは、PHPのパッケージ管理ツールです。
パッケージ管理ツールがあれば、便利なライブラリを簡単に自分のWebサイトに反映することが出来ます。以下のコマンドを実行します。
curl -sS https://getcomposer.org/installer | php
以下のように表示されるまで待ちます。
以下のコマンドを実行します。
sudo mv composer.phar /usr/local/bin/composer
実行は一瞬で完了して、以下のような表示になります。
以下のコマンドを実行します。
composer以下のように表示されれば、composerのインストールは完了です。
必要なライブラリののインストール
以下のコマンドを実行します。
sudo yum install php php-mbstring php-pdo php-gd php-xml以下の表示となったら、「y」を入力し、Enterキーを押下します。
以下の表示になったらインストールは完了です。
laravelのプロジェクトの作成
以下のコマンドを実行します。(「myproject」の部分は好みの名前に変更してください。)
composer create-project --prefer-dist laravel/laravel myproject "5.5.*"以下のように表示されればlaravelのプロジェクトが完成です。
以下のコマンドでプロジェクトのカレントディレクトリに移動します。(「myproject」は、laravelプロジェクト作成時に指定した名前に変えてください。)
cd myproject
以下のコマンドを実行します。
php artisan key:generate以下のように表示されれば成功です。
Webサーバーの起動
以下のコマンドを実行します。
php artisan serve --port=8080以下のように表示されればWebサーバーの起動に成功
Cloud9の右上の「Preview」をクリックして、Preview Running Applicationをクリックする。
Cloud9内部にブラウザが起動し、Laravelの画面が表示される。
参考
https://laraweb.net/environment/3953/
https://dev.classmethod.jp/cloud/aws/my_first_aws_han/
- 投稿日:2019-05-21T22:27:10+09:00
MySQL - [PDO]PHP 経由でメモリリミットを回避し、CSV ファイルのファイルサイズ関係なくダウンロードをできるようにする方法
課題
CSV ダウンロード機能を実装してみたのはいいが、ある程度のファイルサイズになると
メモリリミットでエラーになったり、PHP からの応答待ちでタイムアウトしたりという課題があるこれを CSV のファイルサイズに関係なくダウンロードをできるように改善する方法をスクリプトで残した
参考まで簡単に手元でも確認できるソースを用意してみた
https://github.com/oz-urabe/qiita-for-csv-downloadもともとの問題のある処理
- デフォルトのバッファモードで DB からデータを取得 [解決方法1]
- 取得したデータを配列に格納 [解決方法2]
- 格納した配列を echo などで出力
解決方法1
デフォルトのバッファモードで DB からデータを取得
$stmt = $pdo->prepare('SELECT * FROM dummy'); $stmt->execute();ここで問題となるのが、PHP:バッファクエリと非バッファクエリ から抜粋した以下の記事
クエリは、デフォルトではバッファモードで実行されます。 つまり、クエリの結果がすぐに MySQL サーバーから PHP に転送され、 PHP プロセスのメモリ内に結果を保持し続けるということです。 これで、その後で行数を数えたり結果ポインタを 移動 (シーク) したりといった操作ができるようになります。保持してくれることで、取得後の値を php 側であとから加工しやすくなるが、メモリリミットの問題を引き起こしやすくなるため、デフォルトの挙動が有用であるかはケースバイケース
以下のように追記した
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); $stmt = $pdo->prepare('SELECT * FROM dummy'); $stmt->execute();純粋な PDO で記述しているが、他の DB 接続方法でも同様に対応できる。
また、ORM や フレームワークごとにも対応方法が定義されていると思うので、案件ごとにドキュメントを見てください。解決方法2
取得したデータを配列に格納 という処理ですが、以下のように処理されています。
これは関数の中で配列を作り、それを返り値としています。$csv = []; while ($row = $stmt->fetch()) { $csv[] = sprintf( '%d,%s,%s,%s,%s,%s MB', $row['id'], $row['name'], $row['comment'], $row['created'], $row['updated'], memory_get_peak_usage(true) / 1024 / 1024 ); } return $csv;単純に理解できると思いますが、配列が大きくなるとメモリ消費が大きくなります。
つまり、配列に保存しない方法を考えればよいわけです。while ($row = $stmt->fetch()) { yield sprintf( '%d,%s,%s,%s,%s,%s MB', $row['id'], $row['name'], $row['comment'], $row['created'], $row['updated'], memory_get_peak_usage(true) / 1024 / 1024 ); }
yield
を使うと、その場で配列を生成せず、反復可能なオブジェクトを返します。こうすることでメモリに値を貯め込むことをせず、foreach
の処理内で csv をecho
などで出力するタイミングで値を取得していくような動作となります。※
yield
が使えない古い php の場合においても、配列に溜め込まず、 関数内でecho
するような処理にすればよいです。補足
試しに github に配置したソースを使って、メモリリミットの現象が回避できるか検証していただいてもよいかと思います
- 投稿日:2019-05-21T22:03:22+09:00
今日のおかずをランダムに配信するLINE BOTを作ってみた
だいぶ昔ですが、今日のおかずをランダムに配信するLINE BOTを作ったので、今更ながら紹介します。
概要
今日のおかずをランダムに配信するLINE BOTです。
おかずというのは食べ物ではなく、男にはかかせないアダルトな方のおかずです笑使い方はすごく簡単で、ただ「おかず」とメッセージを送るだけです。
「今日のおかず」とか、「おかずください」といった風に、「おかず」というキーワードが含まれていればOKです。「おかず」というキーワードが含まれていなかったり、スタンプを送ると、"「おかず」って言ってくれたらいいものあげるよ♪"というメッセージを返します。
QRコードはこちらです。
今のところ改修の予定はありませんが、もし反響多ければ、改修を加えることも考えています。
開発した経緯
上記で挙げたおかずですが、ネットを漁れば無料でいくらでも見つかります。
ですが探すのが大変だったり、特にお気に入りのおかずがないという人のために、自動でランダムにおかずを決めてくれたら便利なのではと思い作りました。
またLINE BOT作りに興味もあり、かつPHPも勉強中だったので、それを活かせればと思い、開発に踏み切った次第です。開発環境
実装について
具体的な開発手順(HerokuやLINE Developersへの登録など)は、参考URLにいくつかサイトを挙げてますので、そちらを参照してください。基本的にはそちらと変わりはありません。
ここではオリジナルで実装した部分について紹介します。index.php<?php require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/OkazuGenerator.php'; $httpClient = new \LINE\LINEBot\HTTPClient\CurlHTTPClient(getenv('CHANNEL_ACCESS_TOKEN')); $bot = new \LINE\LINEBot($httpClient, ['channelSecret' => getenv('CHANNEL_SECRET')]); $sign = $_SERVER["HTTP_" . \LINE\LINEBot\Constant\HTTPHeader::LINE_SIGNATURE]; $events = $bot->parseEventRequest(file_get_contents('php://input'), $sign); foreach ($events as $event) { if (!($event instanceof \LINE\LINEBot\Event\MessageEvent) || !($event instanceof \LINE\LINEBot\Event\MessageEvent\TextMessage)) { $bot->replyText($event->getReplyToken(), OkazuGenerator::replyPromptOkazu()); } // おかずと言えばおかずが出てくる if (preg_match("/おかず/", $event->getText())) { $bot->replyText($event->getReplyToken(), OkazuGenerator::replyOkazu()); } else { $bot->replyText($event->getReplyToken(), OkazuGenerator::replyPromptOkazu()); } } ?>index.phpはメイン処理を記載しています。
注目すべきは、foreach文以降の処理です。
最初のif文では、テキスト以外のメッセージが来た場合は全て無視し、催促の文言を返すようにしています。
また、テキストが来た場合に、「おかず」というキーワードが含まれているかも判定して、含まれていればランダムにURLを返し、含まれていなければ先ほどと同様の催促の文言を返します。OkazuGenerator.php<?php /** * おかず生成クラス */ class OkazuGenerator { const URL_ERRY = 'http://erry.one/video/'; const URL_SUGIRL = 'https://sugirl.info/video/'; const URL_SEPARATOR = '/'; const PROMPT_OKAZU = '「おかず」って言ってくれたらいいものあげるよ♪'; /** * おかずをランダムに返す */ public static function replyOkazu() { if (rand(0, 1) === 0) { return OkazuGenerator::URL_SUGIRL . rand(23000, 25515) . OkazuGenerator::URL_SEPARATOR; } else { return OkazuGenerator::URL_ERRY . rand(16000, 18770) . OkazuGenerator::URL_SEPARATOR; } } /** * おかずの催促 */ public static function replyPromptOkazu() { return OkazuGenerator::PROMPT_OKAZU; } } ?>OkazuGenerator.phpは、ランダムにURLを生成したり、催促の文言を定義した、キモとなる処理を記載しています。
仕組みは単純で、アダルトサイトのURLの最後が数字になっているものに対し、ランダムな数字をくっつけているだけです。また、よりバリエーションを増やすため、複数のアダルトサイトに対応させています。Gitリポジトリ
https://github.com/hiesiea/Okazu
参考URL
- 投稿日:2019-05-21T21:48:12+09:00
PHPの基本文法 -関数の引数について-
PHPの基本文法 -関数の引数について-
PHPの関数について、整理してみました。
関数とは
変数は値を入れておく箱でしたが、関数は幾つかの処理をまとめて入れておくものです。
関数には、最初から用意されている「組み込み関数」と、自分で定義する「ユーザ定義関数」があります。「組み込み関数」とは、プログラミング言語などの仕様にあらかじめ用意され、標準で使用できる関数のことを言います。
例えば、isset()
、isNull()
、empty()
,var_dump()
などがそれに当たります。逆に「ユーザー定義関数」とは、ユーザーが作成した関数のことを言います。
例えば、<?php function func() { echo "Test"; } func(); ?>引数について
引数は、関数を呼び出すときに渡す値です。
関数の中では、その値を使用して処理を行い、必要に応じて結果を返します。
PHPの関数は引数のリストにより、関数へ情報を渡すことができる。
このリストは、「,](カンマ)で区切られた式のリストです。
引数の評価は、左から右の順番で行われます。「値渡し」と「参照渡し」について
引数には、「値渡し」と「参照渡し」があります。
「値渡し」は、変数のコピーを渡す方法で、関数側で変数の内容を変更しても、元の変数には影響しない。
「参照渡し」は、変数への参照を渡す方法で、関数側で変数の内容を変更すると、呼び出し側の変数も同様に変更される。//引数「string」を「参照渡し」する関数 function add_str(&$string) { $string = 'Hello ' . $string; } $str = 'World'; add_str($str); echo $str; //Hello Worldが出力されるデフォルト引数値
デフォルト引数値は、引数の既定値です。デフォルト引数値を指定するには、関数の定義において、代入演算子の「=」を使用して、引数にデフォルト値を指定する。PHPでは、配列やNULLをデフォルト値にすることもできる。
function hello($str = "world") { return "Hello $str! <br/>"; } echo hello(); //Hello world echo hello(null); //Hello ! echo hello("Tom"); //Hello Tom!デフォルト引数値を使用する時の注意点
デフォルト引数値は、定数式である必要がある。変数やクラスのメンバーは指定することができない。
またデフォルト引数値を有する引数は、デフォルト引数値がない引数の右側に全てある必要がある。//間違った例 function greet1($str = "Hello", $name) { return "$str $name ! <br/>"; } //第2引数を省略して関数をコールすることはできない echo greet1("Tom"); //正しい用例 function green2($name, $str="Hello") { return "$str $name! <br/>"; } //第2引数を省略して関数をコールすることができる echo greet2("Tom");可変長引数リスト
可変長引数リストは、引数の数が変わる関数を定義するためにサポートされている機能です。
可変長引数リストと使用するには、「func_num_args」関数と「func_get_args」関数を使用する。
サンプル↓<?php function avg() { $sum = 0; for($i = 0; $i < func_num_args(); $i++){ $sum += func_get_arg($i); } $avg = $sum / func_num_args(); var_dump($avg); return $avg; } echo avg(2,1,2,1,3,4,5,3,6); ?>
- 投稿日:2019-05-21T21:23:31+09:00
phpで配列にエラー文を追加したい
phpでお問い合わせフォームを作りながら学習しているのですが、送信ボタンを押された時のエラー表示で躓いたので、学習した内容をメモします!
作りたいもの
- お問い合わせフォーム
- 名前入力欄が空白の場合は、エラー文「お名前を入力して下さい」
- メールアドレス欄が空白の場合は、エラー文「メールアドレスを入力して下さい」
- 上記のエラーを配列に入れて表示したい
phpで配列にエラー文を追加する方法
調査した結果、配列にエラー文を追加するには、以下の2種類の方法があるみたいです!
- 角カッコ[]を使用する方法
- array_push()関数を使用する方法
今回は、角カッコを使用して実装してみたいと思います!
ソースコード
ソースコード<?php $errors = []; if(empty($_POST['name'])){ $errors[] = "お名前を入力して下さい"; } if(empty($_POST['mail'])){ $errors[] = "メールアドレスを入力して下さい"; } ?>上記のように実装したところ、無事、配列にエラー文が格納されました!
参考にしたサイト
- 投稿日:2019-05-21T20:46:59+09:00
heroku + PHP + SendGrid環境でメール送信するのに詰まったメモ
はじめに
自作サイトをherokuにアップしたものの、PHPのmb_send_mail()が動かなかった。
調べてみたところ、そもそもherokuのデフォルト設定ではメール送信には対応してないとのこと。https://qiita.com/negi3d/items/2714534897f90915d392
上記記事を参考に、SendGridアドオンの導入→APIキーの取得云々を済ませコードを追加しても、動かず手詰まりに。
heroku logs --tail
でlogを見てみると、
SendGrid\Emailなんてものはないですよ、と怒られている模様。解決策
SendGridの公式サポートサイトを確認した。
https://sendgrid.kke.co.jp/docs/Integrate/Code_Examples/v3_Mail/php.html
現在はSendGrid\EmailではなくSendGrid\Mailを利用するのが正しいようだ。send.php<?php require 'vendor/autoload.php'; $email = new \SendGrid\Mail\Mail(); $email->setFrom("test@example.com", "Example User"); $email->setSubject("Hello from SendGrid"); $email->addTo("test@example.com", "Example User"); $email->addContent("text/plain", "Hello world"); $sendgrid = new \SendGrid(getenv('SENDGRID_API_KEY')); try { $response = $sendgrid->send($email); print $response->statusCode() . "\n"; print_r($response->headers()); print $response->body() . "\n"; } catch (Exception $e) { echo 'Caught exception: '. $e->getMessage() ."\n"; } ?>
- 投稿日:2019-05-21T19:32:42+09:00
自習用学んだことメモ帳(完全自分用)
- 投稿日:2019-05-21T17:29:52+09:00
51歳からのプログラム 備忘 eloquent データ登録 save()
$model->fill($param)->save();
で、よくしてしまう
save();
の用法ミス。何度も迷走するところ
試行錯誤して時間をかけてしまうので備忘メモ。間違コード
$param[1]=['氏名'=>'山田','住所'=>'北海道','職業'=>'会社員']; $param[2]=['氏名'=>'佐藤','住所'=>'佐賀','職業'=>'主婦']; $param[3]=['氏名'=>'三田','住所'=>'千葉','職業'=>'会社員']; $param[4]=['氏名'=>'井上','住所'=>'長野','職業'=>'自営業']; $param[5]=['氏名'=>'田中','住所'=>'山口','職業'=>'会社員']; $model=new Model;// Modelのインスタンス生成 <- ここが間違い箇所 foreach($param as $key => $value) { $model->fill($value)->save(); }結果は、models(DBテーブル)に一行追加されただけ。
$modelインスタンスは、
models(DBテーブル)の一行分のデータフィールドを操作するインスタンス。
newすることで、新しいデータフィールドを作成して、それをインスタンスで操作する。
newが一度だと、新規に作成する一行しか操作できない。
(一行分のインスタンスしか生成されてない)
上記のコードでは、newは一回だから、
foreachで配列を5回まわしても、
一行のmodels(DBテーブル)に対してしか操作できてない
5行分のmodels(DBテーブル)フィールドを操作するには
5回newしないとね!修正
foreach($param as $key =>$value) { $model=new Model; $model->fill($value)->save(); }
- 投稿日:2019-05-21T17:06:59+09:00
Google reCAPTCHAの実装方法【v2非表示 ver】【Laravel】
はじめに
Bot攻撃による大量申し込みを受けてreCAPTCHAを導入することになったのでその手順を記します。
reCAPTCHAの実装方法がよく分からない..という方は参考になるかもしれません。
v2とv3がありますが、なるべくユーザーによる申し込みを弾かないように、かつ最低限のBot対策はできるようにしたかったので、
v2の非表示バージョンで実装をしました。v2のチェックボックスやv3の方法は今回扱っていません。
分かりやすくするためにPHPフレームワークのLaravelを例にとって説明していますが、他のフレームワークやいわゆるVanilla PHPでも同じように実装できるかと思います。
環境はLAMPを想定しています。
reCAPTCHA Invisibleの設定手順
管理画面での設定
まずはサイトごとのAPIキーを発行しないといけないので管理画面で設定していきます。
Google reCAPTCHAのサイトへアクセス。
https://www.google.com/recaptcha/intro/v3.htmlAdmin consoleへ移動したら、新しくサイトを追加します、
「ラベル」は好きな名前で大丈夫です。
「セキュリティ設定」はレベルが3段階あり、
2~3のレベルで設定してみたところ、ユーザーによる申し込みも画像認証で弾かれてしまうことがあったので、
「ユーザーの負担が最小」である1段階のレベルで設定をすることに決めました。上記で確定すると、APIのサイトキー・シークレットキーが発行されます。
次に、Laravel上で設定作業を行っていきます。
JSタグの設置
まずAPIのJSファイルを読み込みます。
テンプレートファイルの<head>
内に挿入。resources/views/form/input.blade.php<!doctype html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>FooBar Form</title> + <script src="https://www.google.com/recaptcha/api.js" async defer></script> </head> <body> // contents <form action="entry/input/" method="post" id="form"> // form inputs </form> </body> </html>
フロント側のタグ・スクリプト設定
configを設定
APIキーはバージョン管理しないほうがよいので
.env
で設定します。
APP_RECAPTCHA_SITE_KEY
は先ほど管理画面で発行したサイトのキー、
APP_RECAPTCHA_SECRET_KEY
は先ほど発行したシークレットキーです。.env+ APP_RECAPTCHA_SITE_KEY=your_site_key + APP_RECAPTCHA_SECRET_KEY=your_secret_key
設定したファイルを読み込みます。
APIのエンドポイントもここに設定しておきます。config/app.php/* |-------------------------------------------------------------------------- | reCAPTCHA Settings |-------------------------------------------------------------------------- |*/ + 'recaptcha' => [ + 'api_url' => 'https://www.google.com/recaptcha/api/siteverify', + 'site_key' => env('APP_RECAPTCHA_SITE_KEY'), + 'secret_key' => env('APP_RECAPTCHA_SECRET_KEY'), + ], ];
レンダリング対象となるdivタグ設置
reCAPTCHAのレンダリングに必要となるので設定します。
data-sitekey
属性にサイトキー、data-callback
属性にsubmit時のコールバック関数が入ります。resources/views/form/input.blade.php<!doctype html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>FooBar Form</title> <script src="https://www.google.com/recaptcha/api.js" async defer></script> </head> <body> // contents <form action="./entry/input" method="post" id="form"> + <div class="g-recaptcha" + data-sitekey="{{ config('app.recaptcha.site_key') }}" + data-callback="onGrecaptchaSubmit" + data-size="invisible"></div> // form inputs </form> </body> </html>
reCAPTCHAを呼び出すためのAPI設定
コールバック関数をグローバルで定義しておきます。
また、自分の場合はJSによるバリデーションをかけていて、
reCAPTCHAのAPIを発火させるタイミングは「submitボタンが押されたとき」ではなく「バリデーションが通ったとき」にしたいので、reCAPTCHAにより提供されている
grecaptcha.execute()
関数を使用して、任意のタイミングでreCAPTCHAを実行させます。resources/js/form-efo.js+ window.onGrecaptchaSubmit = function(request) { + $('form').submit(); + }; $(() => { // 省略・・・ if (validated()) { // 省略・・・ - $('form').submit(); + grecaptcha.execute(); } // ・・・ });ここまででフロント側の設定は完了です。
ローカル環境で見てみると右下にreCAPTCHAのアイコンが出ているかと思います。
普通に人間が入力してsubmitするとそのまま進めますが、
自動入力などを行うと画像認証画面が出てきて引っかかるようになります。バックエンド側の認証実装
クライアント側だけですと不正に突破される可能性もあるので、バックエンド側でも認証のチェックを行います。
簡単に言いますとクライアント側でreCAPTCHAのAPIが発火した時点でトークンが発行され、
フォームのsubmit時にg-recaptcha-response
というキーでトークンがPOSTされるので、そのトークンをバックエンド側でチェックします。ミドルウェア作成
いろいろ方法はありますが、今回はミドルウェアを利用します。
curl
を使っていますがGuzzle
などのライブラリを使用してもいいかと思います。発行したシークレットキー・クライアント側で発行されたトークン・IPアドレスをもとに、不正なsubmitでないかをチェックします。
APIについて詳しく知りたい方は公式のドキュメント参照してみてください。
https://developers.google.com/recaptcha/docs/verifyapp/Http/Middleware/CheckRecaptcha.php<?php namespace App\Http\Middleware; use Closure; class CheckRecaptcha { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if ($request->input('g-recaptcha-response')){ $secret_key = config('app.recaptcha.secret_key'); $url = config('app.recaptcha.api_url'); $token = $request->input('g-recaptcha-response'); $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query([ 'secret' => $secret_key, 'response' => $token, 'remoteip' => $request->server->get('REMOTE_ADDR'), ]), ]); $_response = curl_exec($ch); curl_close($ch); $response = json_decode($_response); if (isset($response->success) && $response->success) { return $next($request); } else { return redirect('error'); // エラーページへリダイレクト } } return redirect('error'); // エラーページへリダイレクト } }ここからはLaravelだけの話なので、他のフレームワークでしたら飛ばしてオッケーです。
Kernelにミドルウェアを登録し、
app/Http/Kernel.php/** * The application's route middleware. * * These middleware may be assigned to groups or used individually. * * @var array */ protected $routeMiddleware = [ // 省略・・・ + 'recaptcha' => \App\Http\Middleware\CheckRecaptcha::class, ];
申し込みフォームのルーティングにミドルウェアを割り当てます。
routes/web.php- Route::post('/entry/input', [\App\Http\Controllers\EntryInputController::class, 'input'])->name('entry.input'); + Route::post('/entry/input', [\App\Http\Controllers\EntryInputController::class, 'input'])->name('entry.input') + ->middleware('recaptcha');これでreCAPTCHAの認証設定は完了です。
補足:ログを出力する設定
バックエンドでAPI認証する際に、レスポンスの内容を出力したいということがあるかもしれません。
自分の場合は下記のようにログ出力の設定を行いました。
認証エラーが発生した場合ステータスコードと、エラー内容が分かるのでデバッグがやりやすくなるかと思います。app/Http/Middleware/CheckRecaptcha.php/** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if ($request->input('g-recaptcha-response')){ $secret_key = config('app.recaptcha.secret_key'); $url = config('app.recaptcha.api_url'); $token = $request->input('g-recaptcha-response'); $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query([ 'secret' => $secret_key, 'response' => $token, 'remoteip' => $request->server->get('REMOTE_ADDR'), ]), ]); $_response = curl_exec($ch); + $_error = curl_error($ch); curl_close($ch); $response = json_decode($_response); + Log::info( + $_response ? [ + 'recaptcha-result' => $response->success ? $response->success : $response['error-codes'] + ] : ['recaptcha-error' => $_error] + ); + return is_null($response) ? false : $response->success; } return false; }最後に
今回はv2非表示のセキュリティレベル1で設定しましたが、Selenium製ロボットも弾いてくれますし、ユーザーが申し込みできないといったことも今のところはありません。
比較的簡単な実装でBot対策できますので、是非やってみてください。
※参考ページ
https://developers.google.com/recaptcha/docs/invisible
日本語版が無かったので今回まとめてみました。
- 投稿日:2019-05-21T15:32:48+09:00
laravel5.8-slack簡単通知
準備
まず、Slack側でWebhookUrlを作成しておきます。リンク
laravel5.8以降は、composerからSlackチャンネルのインストールが必要なので入れます。
コマンドcomposer require laravel/slack-notification-channel.envファイルにSlackに通知するため、WebhookUrlとChannelを指定する。
.envSLACK_CHANNEL=#test SLACK_WEBHOOK_URL=https://hooks.slack.com/services/自分用設定を変えたので、反映させるためにキャッシュを消します。
コマンドphp artisan config:cache作成
Notification
laravelの機能のNotificationを利用する。
コマンドphp artisan make:notification SlackNotificationSlackNotification<?php namespace App\Notifications; use Carbon\Carbon; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\SlackAttachment; use Illuminate\Notifications\Notification; use Illuminate\Notifications\Messages\SlackMessage; class SlackNotification extends Notification { use Queueable; protected $content; protected $channel; public function __construct($message) { $this->channel = env('SLACK_CHANNEL'); $this->content = $message; } public function via($notifiable) { return ['slack']; } public function toArray($notifiable) { return [ // ]; } public function toSlack($notifiable) { return (new SlackMessage) ->from('管理者') ->image('https://larapet.hinaloe.net/wp-content/uploads/sites/15/2017/03/cropped-86f8cf99663ac01836df2fa9c56890ff-1.png') ->to($this->channel) ->content('Hello') ->attachment(function (SlackAttachment $attachment) { $attachment ->title('ユーザー', '') ->field('名前', 'testUser') ->field('メールアドレス', 'test@co.jp') ->timestamp(Carbon::now()); }); } }SlackAttachmentの参考記事・SlackAttachmentチートシート
・viaメソッド どこのチャンネルに飛ばすか
・toSlackメソッド もしfromの名前を動的に変えたいなどがあれば、constructで指定します。
SlackAttachmentを指定するといろいろ通知を工夫できます。Nortificableをuseしたクラスを用意する。
use Notifiableを記述したクラスならなんでもOK。Modelだとデフォルトでそうなっている場合があるので、基本そこの指定なのかな(しらない) 今回は、別クラスを用意してみます。
app/Services/SlackService<?php namespace App\Services; use Illuminate\Notifications\Notifiable; use App\Notifications\SlackNotification; class SlackService { use Notifiable; public function send($message = "test") { $this->notify(new SlackNotification($message)); } protected function routeNotificationForSlack() { return env('SLACK_WEBHOOK_URL'); } }・routeNotificationForSlack routeNotificationFor○○の○○の部分で書かれたサービス名をもとに、Laravelが自動的にドライバ情報を読み込んでくれる
・send $this->notifyで、SlackNotificationを発火させてる。これで基本準備は完了したので、てきとうなRoute作って呼び出してみる。
web.phpRoute::get('/slackTest', function (){ (new \App\Services\SlackService())->send(); });こんな感じで出れば完了。
参考にさせていただいた記事
https://www.ritolab.com/entry/110
https://qiita.com/10monori/items/a78fb7e270b141c793b1
- 投稿日:2019-05-21T14:07:29+09:00
【初心者】紙に書くというプログラミングの勉強法
未来電子テクノロジーでインターンをしている大学一回生です。
プログラミングを始めてまだ一ヶ月ですが、私なりの勉強法の一部をご紹介します。プログラミングの勉強法
プログラミングを勉強し始める時、Progateを使う方は多いと思います。
Progateは初心者にも優しいサイトとして有名ですが、文系初心者には理解が難しい場面がいくつもありました。
例えば、PHPのオブジェクト指向の基礎のところです。
いきなり「クラス」「インスタンス」「プロパティ」「メソッド」などの聞き慣れない用語が多く出てくるのでここが最初のつまずくポイントでした。
Progateでは、それぞれを分けて優しく解説してくれます。
しかし、メソッドの勉強をした後にプロパティについて復習するとすっかり忘れてしまっているため、またプロパティについての学習を始めなければなりませんでした。
その行為を何回か繰り返した後に、このままでは勉強の効率が悪いと気づきました。
そこで私は、Progateで個別に解説している用語を、自分で紙に書いてまとめ直してみました。
こうすることで「クラス」「インスタンス」「プロパティ」「メソッド」が一目で分かり、復習の効率が上がりました。
コードをわざわざ手書きするのは馬鹿らしいかもしれませんが、必要な情報を簡単に好きな場所に付け足すことができます。
- 投稿日:2019-05-21T13:47:50+09:00
【PHP】案外知られてないかもしれないintegerにキャストした際の挙動まとめ
公式リファレンスマニュアルを今一度読んで仕様を把握する
案外知られてない?みたいな感じだったので、なかなかに引っかかると厄介な挙動3種を以下に1再まとめましたので、
知らなかった方はこの機会に公式リファレンスマニュアルを読んでみてください
という記事です。未知の端数をintegerにキャストすると予期しない結果になる
echo (int) ( (0.1 + 0.7) * 10 ); echo "\n------------------------\n"; echo (float) ( (0.1 + 0.7) * 10 );以下実行結果
7 ------------------------ 8以下float の精度に関する注意から引用
さらに、十進数では正確な小数で表せる有理数、たとえば 0.1 や 0.7 は、 二進数の浮動小数点数としては正確に表現できません。 これは、仮数部をいくら大きくしても同じです。 したがって、それを内部的な二進数表現に変換する際には、どうしても多少精度が落ちてしまいます。 その結果、不思議な結果を引き起こすことがあります。たとえば、 floor((0.1+0.7)*10) の結果はたいてい 7 となるでしょう。おそらくは 8 を想定していらっしゃるでしょうが、そのようにはなりません。 これは、(この計算結果の) 内部的な値が 7.9999999999999991118... のようになっているからです。
よって、小数の最後の桁を信用してはいけませんし、 小数を直接比較して等しいかどうかを調べてはいけません。より高い精度が必要な場合には、 任意精度数学関数または gmp 関数を代わりに使用してください。
詳細は上記のリンク先に書いてあるので読んでおきましょう
キャストは先頭が数字で始まる文字列のみ機能する
echo (int) "5txt"; echo "\n------------------------\n"; echo (int) "before5txt"; echo "\n------------------------\n"; echo (int) "53txt"; echo "\n------------------------\n"; echo (int) "53txt534text"; echo "\n------------------------\n";以下実行結果
5 ------------------------ 0 ------------------------ 53 ------------------------ 53 ------------------------見ての通り、先頭が数字で始まる文字列の数字部分しかキャストされないので、注意が必要です。
文字列から整数への変換に関しては以下を参考にしてください
数値リテラルの先頭「0」は「8進数」と認識されるが文字列の場合その限りではない
echo "\n[先頭0の数値]------------------------\n\n"; var_dump(0123); echo "\n[先頭0の文字列]------------------------\n\n"; var_dump("0123" + 0);以下実行結果
[先頭0の数値]------------------------ int(83) [先頭0の文字列]------------------------ int(123)数値の先頭に
0
を付けると8進数として認識され、83
((3*1) + (2*8) + (1*64) + (0*512)
)と出力されます。
しかし、文字列で先頭に0
を付け、integerにキャストして出力すると123
と、8進数と認識されずにそのまま出力されるので注意する必要があります。おわり
- 何か間違いあれば教えて欲しいです(自分も完全に理解しているわけじゃないので・・・)
- ↑で書いた処理をまとめたやつ置いときます → https://3v4l.org/Wql54
参考
- PHP: 整数 - Manual:整数への変換
- float の精度に関する注意
- PHP: 整数 - Manual:Converting to an integer works only if the input begins with a number
- PHP: 文字列 - Manual(文字列の数値への変換)
- PHP: 整数 - Manual:A leading zero in a numeric literal means "this is octal". But don't be confused: a leading zero in a string does not.
公式リファレンスマニュアルに全部かいてあることなので ↩
- 投稿日:2019-05-21T13:35:02+09:00
php-master-changes 2019-05-20
今日は *.re へ変えていたファイルの *.l への再リネーム、テストの修正があった!
2019-05-20
krakjoe: Revert "Rename *.l files to *.re"
- https://github.com/php/php-src/commit/e11233dc492a96844d22e4a3ed8e9e2d8a36651c
- [7.4~]
- 先日の修正 をリバート
- PR:4172 で地味に喧嘩しててふいた
- 「*.l で分かるからわざわざ変えなくていいんじゃない?」と俺も最初思ったんだけど、よーく考えると本当はこれ petk の人、というか統一が本来正しい気もする(結局リバートされちゃったけど)
- 分かってる人にとってはどっちでもよい奴で、分かってない新規の人の参入障壁を下げるため構造を単純化していく、という話であって、分かってる人に分かってない人の気持ちは分からないので、その取り組みにはシンプルな原則論(同じ種類のファイルは同じ種類に見えるよう命名するとか)へ立ち返っての対応が適切
- 外部ドキュメントなんて必ず時代遅れになるもんだし、GitHub からのファイルのコミットログの見え方はともかく blame は見れるので、気にせず統一の方向に直しちゃってよかったんじゃないかな、みたいな(ただどちらかというと *.re は親しみないけど *.l は親しみあるので、もう *.l 統一でよかったんじゃないかな、みたいな)
- まあ当事者たちも言ってるように、ぶっちゃけどうでもいい些細な話だけどね!
cmb69: Fix test wrt. opcache.error_log
- https://github.com/php/php-src/commit/64ec9f4dbe26ee587494b9faf510556e34aa875f
- [7.2~]
- ext/opcache で、テストで opcache.error_log をクリアするよう修正
- 投稿日:2019-05-21T12:37:24+09:00
EC-CUBE3でデバッグモードを利用する手順【index_dev.php不要】
先日ECCUBE3で構築されているサイトを触ることになったのですが、
- index_dev.php が無い!?
dump()
が使えない(本番環境と同じ設定になっている?)- Warningエラーが既存で大量発生していてローカル環境が見れない
というめまいの催すような環境だったのでデバッグモード設定の手順をまとめます。
ECCUBE3を触るときにindex_dev.phpを付けてデバッグするのが一般的だと思いますが、
毎回URL打つのは手間なので、URL変える必要なくデバッグモードを設定していきます。手順
config.ymlを修正
デバッグモードをconfigで設定できるようにします。
app/config/eccube/config.ymlauth_magic: xxxxx password_hash_algos: sha256 shop_name: the_shop force_ssl: 1 admin_allow_host: { } cookie_lifetime: 0 locale: ja timezone: Asia/Tokyo eccube_install: 1 option_favorite_product: 0 + debug: true
Application側でconfigを読み込み
yamlで設定した値を読み、
config: true
であればsymfonyのデバッグハンドラーを呼び出しています。
Application.phpのinitialize()
メソッドであればどこに記載してもよいかと思います。src/Eccube/Application.phppublic function initialize() { // // ... // + if (isset($this['config']['debug']) && $this['config']['debug']) { + $this['debug'] = true; + Debug::enable(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_WARNING); + }
TwigServiceProviderの設定変更
これまででデバッグモード自体は有効になりましたが、
Twig上で発生している既存のNoticeやWarningエラーを無視したいので下記を設定します。vendor/silex/silex/src/Silex/Provider/TwigServiceProvider.php/** * Twig integration for Silex. * * @author Fabien Potencier <fabien@symfony.com> */ class TwigServiceProvider implements ServiceProviderInterface { public function register(Application $app) { $app['twig.options'] = array(); $app['twig.form.templates'] = array('form_div_layout.html.twig'); $app['twig.path'] = array(); $app['twig.templates'] = array(); $app['twig'] = $app->share(function ($app) { $app['twig.options'] = array_replace( array( 'charset' => $app['charset'], 'debug' => $app['debug'], - 'strict_variables' => $app['debug'], + 'strict_variables' => false, ), $app['twig.options'] );最後に
とりあえず、これでデバッグ使えるようにはなりました。
ECCUBE3のデバッグで困っている..という方は試してみてはどうでしょうか。
- 投稿日:2019-05-21T09:22:53+09:00
素因数分解をPHPでやってみた
はじめに
私は、現在大学4回生です。
未来電子テクノロジーのインターンでプログラミング開発コースに所属しています。
今回は、アルゴリズムの勉強中にひっかかった素因数分解についてお話しします。プログラミング初心者であるため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するのでどんどん指摘してください。素因数分解とは
素因数分解は、自然数を素数で表すことを示します。
(素数は、1とその数でしか割れない数字のことを言います)例)
20 = 2 × 2 × 5
63 = 3 × 3 × 7
32 = 2 × 2 × 2 × 2このように素数が並びます。
PHPで実装
PHPでのコードは、以下のものです。
function pf($n){ $result = array(); if(n===1){ return [1]; } $init = 2; while($n! == 1){ $i = $init; while($i < 0 *FFFFFF){ if($n%$i == 0){ $result[] = $i; break; } $i++; } $init =$i; } return $result; }まとめ
今回は、素因数分解についてお話ししました。
whileが2回入ってくるあたりや、変数がどこからくるのかが初め理解できませんでした。
コードは、理解してこそ自分のものになると思います。参考文献
http://sevendays-study.com/algorithm/pr-day7.html
「一週間で身につくアルゴリズムとデータ構造」
- 投稿日:2019-05-21T05:48:51+09:00
開発環境を整える
開発環境を整える
[windows環境]
MAMP
mySQLworkbench
PHPStorm
Composer
Lalavel installer~補足~
office Excel
office Word
office PowerPoint(社員用)
メーラー[ダウンロードリンク]
MAMP
https://www.mamp.info/en/
mySQLworkbench
https://dev.mysql.com/downloads/workbench/
PHPStorm
https://www.jetbrains.com/phpstorm/
Composer
https://getcomposer.org/download/設定で詰まったところ
デバッグの設定
php.iniの設定
[ブラウザ上で有効にする方法]
PHPにXdebugを有効にする時、PHPの設定を変更しないといけない。
PHPの実行ファイルはphp.exe。設定ファイルはphp.ini。
通常PHPをダウンロードした場合、インストールしたフォルダー内の直下に両方とも存在する。
しかしMAMPの場合phpがたくさん入っているので、直下にはなく、bin/php/php{version名}の中にphp.exeファイルがあり、conf/php{version名}の中にphp.iniファイルがある。php.iniにxdebugを有効にするには
php.iniの一番下に下記を加える[Xdebug] zend_extension = "php_xdebug.dll" xdebug.remote_enable = 1[設定確認]
設定確認はMAMPを起動してpreference(設定)をクリックWebServerタグをクリックdocumentRootのフォルダーパスを確認し、そのフォルダーの下にindex.phpファイルを作成index.php<?php phpinfo(); ?>ブラウザのURLに[ loaclhost ]と入れれば 下のほうにxdebugの存在が確認できる。
[PHPStormの上で有効にする]
PHPStormではphpのチェックを行うことができる。
しかしPHPStormの設定でphp.exeファイルを指定する際、PHPStorm側で同じファイル内にphp.iniファイルがないか検索をされる。なければエラーが返ってくるので、先ほど編集を加えたphp.iniファイルをコピーして、php.exeと同じフォルダーに貼付けてあげると上手く通る[composerをインストールする際]
php.exeを指定する。
インストールする際にエラーが返ってきた。
[現状:]
php/ext/php_mysql.dllがないとのことで
フォルダーを探しても見つからず、
php.ini内を見ると、
extension=php_mysql.dll
が存在していた。
[対応:]
頭に;(コメントアウト)をつけて上手く動いたchorome上でXdebug helper有効にする
Xdebug helperの拡張をすることで、リモートデバッグを使うことができるようになる
choromeでlaravelをインストールする際
コマンドプロンプト上で
laravel new {プロジェクト名}と入力すると起動するが、よくエラーで落ちるので
(プロジェクト名がフォルダー名になる)composer create-project laravel/laravel {プロジェクト名} --prefer-distのほうが使える
- 投稿日:2019-05-21T01:25:13+09:00
Pivotal TrackerとSlackを連携する
説明
- Pivotal Trackerでの更新をSlackで受け取りたいがIntegrationが見つからない :(
- https://www.pivotaltracker.com/integrations/slack はあったけど、これはFrom:Slack => To:Pivotal Trackerというかんじ。
- とにかく連携する方法が見つからないので書いてみた
- Pivotal Tracker側では
Activity Web Hook
をAddする![]()
- Slack側では
Incoming WebHooks
を設定する![]()
- あとはコードをインストール、設定すると、Pivotal Tracker側の更新がSlackに通知される :)
![]()
コード
https://github.com/yuki777/pivotaltracker-slack
... public function run() { $activity = json_decode(file_get_contents("php://input"), true); if(! $this->validate($activity)) return false; $this->sendSlack($this->getSummary($activity)); } private function validate($activity) { if(! isset($activity['kind'])) return false; if( ($activity['kind'] != 'story_create_activity') and ($activity['kind'] != 'story_update_activity') and ($activity['kind'] != 'task_create_activity') and ($activity['kind'] != 'task_update_activity') and ($activity['kind'] != 'task_delete_activity') and ($activity['kind'] != 'comment_create_activity') ) return false; if(! isset($activity['message'])) return false; if(! isset($activity['highlight'])) return false; if(! isset($activity['primary_resources'][0]['name'])) return false; if(! isset($activity['primary_resources'][0]['story_type'])) return false; if(! isset($activity['primary_resources'][0]['url'])) return false; if(! isset($activity['performed_by']['name'])) return false; return true; } private function getSummary($activity) { $summary = [ 'user' => $activity['performed_by']['name'], 'message' => $activity['message'], 'highlight' => $activity['highlight'], 'story_name' => $activity['primary_resources'][0]['name'], 'story_type' => $activity['primary_resources'][0]['story_type'], 'story_url' => $activity['primary_resources'][0]['url'], ]; if(isset($activity['changes'][0]['new_values']['description'])){ $summary['description'] = $activity['changes'][0]['new_values']['description']; } return $summary; } private function sendSlack($summary) { $settings = ['link_names' => true]; $client = new Nexy\Slack\Client($_ENV['SLACK_WEBHOOK_URL'], $settings); $fields = [ new \Nexy\Slack\AttachmentField('User', $summary['user'], true), new \Nexy\Slack\AttachmentField('Highlight', $summary['highlight'], true), new \Nexy\Slack\AttachmentField('Story Name', $summary['story_name']), new \Nexy\Slack\AttachmentField('Message', $summary['message']), ]; if(isset($summary['description'])){ $fields[] = new \Nexy\Slack\AttachmentField('Description', $summary['description']); } return $client->attach((new \Nexy\Slack\Attachment())->setFields($fields))->send($summary['story_url']); } ...