- 投稿日:2021-07-15T22:07:42+09:00
PHP 自動販売機ロジック
Every Qiita #13 のんびり独学初学者の毎日投稿チャレンジ 13日目 今回は・・・ 自動販売機ロジックを考えたときの備忘録です。 処理内容 お釣りを貨幣毎に何枚必要かを計算し出力します。不足の場合不足金を出力します。 $purchase = 10000; // 支払い金額 $product = 9000; // 購入金額 function calculation($purchase, $product){ $money = [10000,5000,1000,500,100,50,10,5,1]; $change = $purchase-$product; $result = []; if ($change < 0) { return $change *= -1; } foreach ($money as $value) { if($change <= 0){ $result[$value] = 0; }else{ $page = floor($change/$value); $result[$value] = $page; $change -= ($page*$value); } } return $result; } 返り値には貨幣をキーとして、値に対応する枚数を指定しています。 解説 貨幣の種類を格納した配列を用意します。 お釣りを計算 返り値は配列になるので、空の配列を定義します。 お釣りが0円未満の場合は-1を掛けてreturnします お釣りが0円以下の場合は枚数=0に設定し、全ての貨幣と枚数を出力できるようにします。 お釣りを貨幣で割った整数値が枚数になるのでfloor関数で枚数を計算します。←そのまま返り値に格納します。 貨幣×枚数をお釣りから引き、お釣りを再定義します。 ※4〜6を全ての貨幣でループ処理させます。
- 投稿日:2021-07-15T20:04:31+09:00
【Laravel】DBから取得したCollection型の空判定
はじめに PHP、Laravel初学者である私だが、他の方のコードをレビューする機会があった。 そこで、Collection型と配列で空のチェックが異なることを知ったため、ここに記載する。 結論 Collection型と配列の空を判定する方法は以下の通り。 ■ Collectionの空チェック $collection = collect([]); $collection->isEmpty(); // true ■ 配列の空チェック $array = []; empty($array); // true 詳細 Collection型は、LaravelにあるObject型の一種であり、配列のラッパーである。例えば、DBから値を複数取得した結果(クエリビルダ等を用いた際)の型がCollectionである。また、Collection型にはメソッドが存在しており、今回は、Collectionが持っているメソッドを用いて空かどうかを判定している。 ■ 備考 因みに、今回はisEmpty()を紹介したが、isNotEmpty()も存在する。 他にも、Collectionのメソッドは複数あるため、興味があれば以下公式ページを参照して欲しい。
- 投稿日:2021-07-15T16:50:29+09:00
php validation class
<?php /** * helper class for ValidFluent */ class validFluentObj { public $value; public $error; function __construct($value) { $this->value = $value; $this->error = ''; } } /** * */ class ValidFluent { public $isValid = TRUE; public $isGroupValid = TRUE; public $validObjs; //array of validFluentObj private $currentObj; //pointer to current validFluentObj , set by ->name() //default error messages private static $error_required = 'This field is required'; private static $error_date = 'Please enter a date in the YYYY-MM-DD format'; private static $error_email = 'Please enter a valid email'; private static $error_url = 'Please enter a valid url'; private static $error_alfa = 'Only letters and numbers are permited'; private static $error_text = 'Only letters are permited'; private static $error_minSize = 'Please enter more than %s characters'; private static $error_maxSize = 'Please enter less than %s characters'; private static $error_numberFloat = 'Only numbers are permitted'; private static $error_numberInteger = 'Only numbers are permitted'; private static $error_numberMax = 'Please enter a value lower than %s '; private static $error_numberMin = 'Please enter a value greater than %s '; private static $error_oneOf = 'Please choose one of " %s "'; private static $error_equal = 'Fields did not match'; private static $error_regex = 'Please choose a valid value'; // some regEx's private $pattern_email = '/^([a-zA-Z0-9_\+\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/'; private $pattern_url = '/^((http|ftp|https):\/\/)?www\..*.\.\w\w\w?(\/.*)?(\?.*)?$/'; //check... private $pattern_alfa = '/^[a-zA-Z0-9_\-\. ]+$/'; private $pattern_alfa = '/^(\d|\-|_|\.| |(\p{L}\p{M}*))+$/u'; private $pattern_text = '/^( |(\p{L}\p{M}*)+$/u'; private $pattern_numberInteger = '/^[\+\-]?[0-9]+$/'; private $pattern_numberFloat = '/^[\+\-]?[0-9\.]+$/'; private $pattern_date = '/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/'; /** * * @param Array $post ($Key => $value) array */ function __construct($post) { foreach ($post as $key => $value) { $this->validObjs[$key] = new validFluentObj(trim($value)); } } /** * Helper: returns TRUE if last valiadtion passed , else FALSE * @return Boolean */ function isValid() { return $this->isValid; } /** * Helper: returns TRUE if all validations passed , else FALSE * @return Boolean */ function isGroupValid() { return $this->isGroupValid; } /** * Return's $name validation error * @param string $name * @return string the error */ function getError($name) { if (isset($this->validObjs[$name])) return $this->validObjs[$name]->error; return ''; } /** * Returs $name value * @param string $name * @return string the value */ function getValue($name) { if (isset($this->validObjs[$name])) return $this->validObjs[$name]->value; return ''; } /** * Used to set starting values on Form data * ex: $valid->name('user_name)->setValue($database->getUserName() ); * @param string $value */ function setValue($value) { $this->currentObj->value = $value; return $this; } /** * used to set error messages out of the scope of validFluent * ex: $valid->name('user_name')->setError('The Name "Andre" is already taken , please try another') * @param string $error */ function setError($error) { $this->currentObj->error = $error; $this->isGroupValid = FALSE; $this->isValid = FALSE; return $this; } /** * PRIVATE Helper to set error messages * @param string $errorMsg custom error message * @param string $default default error message * @param string $params extra parameter to default error message */ private function setErrorMsg($errorMsg, $default, $params=NULL) { $this->isGroupValid = FALSE; if ($errorMsg == '') { $this->currentObj->error = sprintf($default, $params); } else $this->currentObj->error = $errorMsg; } //////////////////////////////////////////////// /////////////////////////////////////////////// //// /// Validation Functions // /** * Used to set a pointer for current validation object * if $name doesnt exits, it will be created with a empty value * note:validation always pass on empy not required fields * @param string $name as in array($name => 'name value') * @return ValidFluent */ function name($name) { if (!isset($this->validObjs[$name])) $this->validObjs[$name] = new validFluentObj(''); $this->isValid = TRUE; $this->currentObj = &$this->validObjs[$name]; return $this; } /** * Note if field is required , then it must me called right after name!! * ex: $valid->name('user_name')->required()->text()->minSize(5); * @param string $errorMsg * @return ValidFluent */ function required($errorMsg=NULL) { if ($this->isValid) { $this->isValid = ( $this->currentObj->value != '') ? TRUE : FALSE; if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_required); } return $this; } /** * validates a Date in yyyy-mm-dd format * @param string $errorMsg * @return ValidFluent */ function date($errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $this->isValid = (preg_match($this->pattern_date, $this->currentObj->value)) ? TRUE : FALSE; if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_date); } return $this; } /** * validates an email address * @param string $errorMsg * @return ValidFluent */ function email($errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $this->isValid = (preg_match($this->pattern_email, $this->currentObj->value) > 0) ? TRUE : FALSE; if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_email); } return $this; } /** * validates a URL address * @param string $errorMsg * @return ValidFluent */ function url($errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $this->isValid = (preg_match($this->pattern_url, $this->currentObj->value)) ? TRUE : FALSE; if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_url); } return $this; } /** * ex: ->regex('/^[^<>]+$/', 'ERROR: < and > arent valid characters') * @param string $regex a regular expresion '/regex/' * @param string $errorMsg * @return ValidFluent */ function regex($regex, $errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $this->isValid = (preg_match($regex, $this->currentObj->value)) ? TRUE : FALSE; if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_regex); } return $this; } /** * ex: ->name('password')->equal('passwordConfirm' , 'passwords didnt match') * @param string $value2 * @param string $errorMsg * @return ValidFluent */ function equal($value2, $errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $this->isValid = ($value2 == $this->currentObj->value); if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_equal); } return $this; } /** * Ex: ->oneOf('blue:red:green' , 'only blue , red and green permited') * *case insensitive* * @param string $items ex: 'blue:red:green' * @param string $errorMsg * @return ValidFluent */ function oneOf($items, $errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $item = explode(':', strtolower($items)); $result = array_intersect($item, array(strtolower($this->currentObj->value))); $this->isValid = (!empty($result)); if (!$this->isValid) { $itemsList = str_replace(':', ' / ', $items); $this->setErrorMsg($errorMsg, self::$error_oneOf, $itemsList); } } return $this; } ///////////////////////////////////////////////////// //////////////////////////////////////////////////// //// /// text validation // /** * Only allows A-Z a-Z and space * @param string $errorMsg * @return ValidFluent */ function text($errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $this->isValid = (preg_match($this->pattern_text, $this->currentObj->value)) ? TRUE : FALSE; if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_text); } return $this; } /** * Only allows A-Z a-z 0-9 space and ( - . _ ) * @param string $errorMsg * @return ValidFluent */ function alfa($errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $this->isValid = (preg_match($this->pattern_alfa, $this->currentObj->value)) ? TRUE : FALSE; if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_alfa); } return $this; } // same function, better spelled function alpha($errorMsg=NULL) { $this->alfa($errorMsg); return $this; } /** * @param int $size the maximum string size * @param string $errorMsg * @return ValidFluent */ function maxSize($size, $errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $this->isValid = (strlen($this->currentObj->value) <= $size); if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_maxSize, $size); } return $this; } /** * @param int $size the minimum string size * @param string $errorMsg * @return ValidFluent */ function minSize($size, $errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $this->isValid = (strlen($this->currentObj->value) >= $size); if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_minSize, $size); } return $this; } ///////////////////////////////////////////////////// //////////////////////////////////////////////////// //// /// Numbers validation // /** * checks if its a float ( + - . ) permited * @param string $errorMsg * @return ValidFluent */ function numberFloat($errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $this->isValid = (preg_match($this->pattern_numberFloat, $this->currentObj->value)) ? TRUE : FALSE; if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_numberFloat); } return $this; } /** * checks if its a integer ( + - ) permited * @param string $errorMsg * @return ValidFluent */ function numberInteger($errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $this->isValid = (preg_match($this->pattern_numberInteger, $this->currentObj->value)) ? TRUE : FALSE; if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_numberInteger); } return $this; } /** * @param number $max * @param string $errorMsg * @return ValidFluent */ function numberMax($max, $errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $this->isValid = ($this->currentObj->value <= $max) ? TRUE : FALSE; if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_numberMax, $max); } return $this; } /** * @param number $min * @param string $errorMsg * @return ValidFluent */ function numberMin($min, $errorMsg=NULL) { if ($this->isValid && (!empty($this->currentObj->value))) { $this->isValid = ($this->currentObj->value >= $min) ? TRUE : FALSE; if (!$this->isValid) $this->setErrorMsg($errorMsg, self::$error_numberMin, $min); } return $this; } } ?>
- 投稿日:2021-07-15T16:40:52+09:00
PHPで処理の進捗状況を表示し、処理終了時に自動でダウンロードする処理のサンプル
概要 時間のかかる処理(ファイル出力やバッチ処理など)の処理状況を随時送信し使用者のストレスを軽減する。また、デバッグが簡単。 処理概要 PHPでHTMLの<head>をブラウザに送信 ブラウザでHTMLの<head>に記述されたスクロール処理を永久ループ開始 PHPの処理状況をブラウザに随時送信 ブラウザ側で<body>内の行が増え自動的に最新状況へスクロール PHPの出力が終了するとブラウザでDOMContentLoadedが呼ばれる DOMContentLoaded内にてスクロール処理の停止(フラグ) DOMContentLoaded内にてダウンロード用の<a>タグをクリック ファイルのダウンロードリクエストを行う ブラウザのファイル保存ダイアログが表示される サンプルプログラムのキャプチャ Qiita記事埋め込み用 pic.twitter.com/v6ZTTLf6UU— やみ姐さん? (@Yamine1San) July 15, 2021 サンプルプログラムが動作するURL サンプルプログラムが動作するURL ※私のパソコンが起動していると開ける サンプルプログラム 処理概要1~7の処理 echo_flush_download.php <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>処理の進捗状況の表示と完了後のダウンロードサンプル</title> <script> let flg_dom_content_loaded = false; window.addEventListener('DOMContentLoaded', function () { flg_dom_content_loaded = true; [].forEach.call(document.getElementsByClassName('onload_click'), function (x) { x.click(); }); }); loopprocess(); /** * 全コンテンツの出力が終了するまで下へスクロールし続けさせる */ function loopprocess() { window.scrollTo(0, 1000000); if (true === flg_dom_content_loaded) { return; } setTimeout('loopprocess()', 300); } </script> </head> <body> <?php // <head>の送信 ob_flush(); flush(); function echo_flush($str) { echo $str; ob_flush(); flush(); } echo_flush('<h1>CSV作成処理を開始しました。</h1>'); $csv_nm = 'test.csv'; $csv_path = dirname(__FILE__).'/'.$csv_nm; $handle = fopen($csv_path, 'w'); $start_time = microtime(true); $total = 3280; $limit = 100; $last_page = ceil($total / $limit); echo_flush( sprintf( "<div>%s: %d件の処理開始 現在のメモリ使用量:%sMB 最大メモリ使用量:%sMB</div>" , date('Y-m-d H:i:s') , $total , round(memory_get_usage() / pow(1024, 2), 1) , round(memory_get_peak_usage() / pow(1024, 2), 1) ) ); for ($page_no = 1; $page_no <= $last_page; $page_no++) { $page_start = (($page_no - 1) * $limit) + 1; $page_last = $page_no * $limit; if ($total < $page_last) $page_last = $total; echo_flush( sprintf( "<div>%s: ページ%d/%d レコード%d~%d/%d件のデータ取得開始 現在のメモリ使用量:%sMB 最大メモリ使用量:%sMB 合計時間 %s秒</div>" , date('Y-m-d H:i:s') , $page_no , $last_page , $page_start , $page_last , $total , round(memory_get_usage() / pow(1024, 2), 1) , round(memory_get_peak_usage() / pow(1024, 2), 1) , round((microtime(true) - $start_time), 2) ) ); // データ取得クエリ実行 // $rs = find_many(); usleep(500000); echo_flush( sprintf( "<div>%s: ページ%d/%d レコード%d~%d/%d件のデータ取得終了 現在のメモリ使用量:%sMB 最大メモリ使用量:%sMB 合計時間 %s秒</div>" , date('Y-m-d H:i:s') , $page_no , $last_page , $page_start , $page_last , $total , round(memory_get_usage() / pow(1024, 2), 1) , round(memory_get_peak_usage() / pow(1024, 2), 1) , round((microtime(true) - $start_time), 2) ) ); // ランダムでエラー出力 mt_srand(); if (0 === mt_rand(0, 8)) { $error_msg = date('Y-m-d H:i:s').': '.$page_start.'行目でエラーが発生しました。 カラム〇○の値が未設定のためスキップしました。'; echo_flush('<div style="font-weight: bold;color:red;">'.$error_msg.'</div>'); } // CSV追加 // foreach ($rs as $r) { // fputcsv(); // } fputs($handle, mb_convert_encoding(sprintf( "%s: ページ%d/%d レコード%d~%d/%d件のデータの書き込み 現在のメモリ使用量:%sMB 最大メモリ使用量:%sMB 合計時間 %s秒" , date('Y-m-d H:i:s') , $page_no , $last_page , $page_start , $page_last , $total , round(memory_get_usage() / pow(1024, 2), 1) , round(memory_get_peak_usage() / pow(1024, 2), 1) , round((microtime(true) - $start_time), 2) ), 'SJIS-win', 'UTF-8')."\r\n" ); } fclose($handle); echo_flush( sprintf( "<div>%s: ページ%d/%d レコード%d件の処理終了 現在のメモリ使用量:%sMB 最大メモリ使用量:%sMB 合計時間 %s秒</div>" , date('Y-m-d H:i:s') , $page_no , $last_page , $total , round(memory_get_usage() / pow(1024, 2), 1) , round(memory_get_peak_usage() / pow(1024, 2), 1) , round((microtime(true) - $start_time), 2) ) ); echo_flush('<h2>CSVファイルダウンロードリンク:<a href="./echo_flush_download_file.php?file='.$csv_nm.'" class="onload_click" download="CSVファイル.csv">CSVファイル.csv</a></h2>'); echo_flush('<h1>CSV作成処理を終了しました。</h1>'); 処理概要8の処理 echo_flush_download_file.php <?php // TODO CSVを出力した人によるダウンロードリクエストなのか要確認 $csv_path = dirname(__FILE__).'/test.csv'; header("Content-Disposition: attachment; filename=\"CSVファイル.csv\""); header('Content-Length: '.filesize($csv_path)); header('Content-Type: application/force-download;'); readfile($csv_path);
- 投稿日:2021-07-15T16:05:18+09:00
PHPunitを使用しテストを行う際のエラー
課題 phpunitを使いテストを行う際に下記のエラーが発生。 /usr/bin/php declares an invalid value for PHP_VERSION. This breaks fundamental functionality such as version_compare(). Please use a different PHP interpreter. 考えたこと エラー文にて invalid value for PHP_VERSION. PHPのバージョンが無効な値 →phpのバージョンが原因ではないかと推測。 phpのバージョンを確認 php -v WARNING: PHP is not recommended PHP is included in macOS for compatibility with legacy software. Future versions of macOS will not include PHP. PHP 7.3.24-(to be removed in future macOS) PHP 7.3.24は将来的にmacOSで削除されるという警告が出ます。 →phpのバージョンをアップデートすれば解消されるのではないか? 対処法 PHPのバージョンがいけなかったようなのでアップデートします。 homebrewのインストール ※すでにhomebrewをインストールしている方は飛ばしてください。 インストールする際に以下の記事を参考にさせていただきました。 https://qiita.com/zaburo/items/29fe23c1ceb6056109fd php 7.4をインストール brew search php7 インストールできるバージョンを確認 php@7.4があるので brew install php@7.4 インストールは以上になります。 パスを通す シェルの確認を行う cd ~ echo $SHELL /bin/zshと表示された場合 echo 'export PATH=/usr/local/opt/php@7.4/bin:$PATH' >> ~/.zshrc source ~/.zshrc /bin/bashと表示された場合 echo 'export PATH="/usr/local/opt/php@7.4/sbin:$PATH"' >> ~/.bash_profile source ~/.bash_profile を実行しパスを反映させます。 バージョンの確認 php -v バージョンがアップされているかどうか確認を行います。 バージョンが変わっていれば成功です。 まとめ PHP 7.3.24に原因があると思い調査しましたが、根本的な原因を解明することはできませんでした。 おそらくですが、macOSでPHP 7.3.24を使用している場合起こるエラーになります。 PHPのバージョンの問題であると推測し、バージョンを変更することによってエラーを解決しました。 参考 https://blog-and-destroy.com/29755 https://qiita.com/kuroro_ru/items/046cba8e23d32599ee4a https://qiita.com/zaburo/items/29fe23c1ceb6056109fd
- 投稿日:2021-07-15T13:29:09+09:00
PHP ローカルスコープ内で変数名を使い回すのはなるべくやめようと先輩から教わった
目的 先輩から教わったことをメモ的にまとめる ご注意 本ソースは他のエンジニアの方が書いてくださったものです。 ソースの設計思想はいくつか存在し、正解はいくつもあります。 本記事は筆者個人の意見であり、登場人物である先輩の考えも実際とは異なる可能性があります。 例として記載するソースは元のものからかなり修正をしております。 情報 下記の様なソースが記載されていた。 public function foo() { $adminUserInfos = $this->userService->getAllAdminUserInfo(); foreach($adminUserInfos as $adminUserInfo) { $user_id = $adminUserInfo->id; $user_name = $adminUserInfo->name; $this->mailService->sendMail($user_id, $user_name); } $userInfos = $this->userService->getAllUserInfo(); foreach($userInfos as $userInfo) { $user_id = $userInfo->id; $user_name = $userInfo->name; $this->mailService->sendMail($user_id, $user_name); } } 先輩が言っていたことと筆者の初見の所感 「ローカルスコープ内で変数は使い回さないほうが良いかもしれない」 どういうことかというと、上記のソースは1つ目のforeachで$user_idと$user_nameを定義して他メソッドの引数として使っている。 更に2つ目のforeachで$user_idと$user_nameを更に定義している。 筆者は問題ないと思ってしまった。何なら処理に一貫性があって良いソースだと思った。 先輩曰く「1つ目のforeachが終わった後に$userInfos = $this->userService->getAllUserInfo();エラーかなにかでスルーされてしまったら事故になる可能性がある」ということだ。 上記のソースに限っては$userInfosがnullなどだった場合、foreachが動作することは無い。 しかしながら、先輩はこのソースに限った話ではなく、そもそも「ローカルスコープ内で変数名を使い回すと事故につながる可能性になるのでやめたほうが良い」と言っていると筆者は受け取った。 個人的に非常に学びになった。 どのように記載すると事故は防げるのか 今回のソースで言うと2個回避策があると思う。 下記に先輩が提案されていた回避策を記載する。 public function foo() { $adminUserInfos = $this->userService->getAllAdminUserInfo(); foreach($adminUserInfos as $adminUserInfo) { $this->mailService->sendMail($adminUserInfo->id, $adminUserInfo->name); } $userInfos = $this->userService->getAllUserInfo(); foreach($userInfos as $userInfo) { $this->mailService->sendMail($userInfo->id, $userInfo->name); } } 先輩は変数に入れずにそのまま直接引数として使用する方法を挙げられていた。その後使う予定の無い変数を増やすより事故は減りそうである。 下記に筆者が考えた回避策を記載する。 public function foo() { $adminUserInfos = $this->userService->getAllAdminUserInfo(); foreach($adminUserInfos as $adminUserInfo) { $admin_user_id = $adminUserInfo->id; $admin_user_name = $adminUserInfo->name; $this->mailService->sendMail($admin_user_id, $admin_user_name); } $userInfos = $this->userService->getAllUserInfo(); foreach($userInfos as $userInfo) { $user_id = $userInfo->id; $user_name = $userInfo->name; $this->mailService->sendMail($user_id, $user_name); } } 筆者は単純に変数を使い回さないようにした。ただそれだけ。 感想 先輩とソースを一緒に読むと非常に学びになることがわかった。 どうにかして一緒にソースを読んで意見を出し合う時間を作りたいのだけれど、、、なかなか難しい。
- 投稿日:2021-07-15T11:22:37+09:00
TwitterAPI研究
APIとは APIとは、ソフトウェアやアプリケーションなどの一部を外部に向けて公開することにより、第三者が開発したソフトウェア機能を共有できるものです。 上記だとまだ難しいと思うので、下記サイトをご覧ください。APIとはなにかを例を交えて、イメージしやすいようにまとめてあり、とてもわかりやすいです。 ※参考 https://wa3.i-3-i.info/word12428.html Twitter APIとは ツイートやタイムラインの取得、リツイートやいいねといったTwitterサービスを、公式のウェブサイトを経由せずに直接利用できるサービスです。 例えば、ツイートやリツイート、タイムラインの取得などウェブサイトにログインしてから行うと思います。しかし、TwitterAPIを用いることで、ウェブサイトを経由しなくても、ツイートなどを取得することができます。 ここで「Twitterアプリを使っているからウェブサイトは経由しない」という方がほとんどだと思います。しかし、そのアプリこそがAPIの活用例の一つなのです。 Twitter APIの機能 TwitterのAPIは大きく5つのAPI機能に分かれています。 ・キーワード検索できる「サーチAPI」 ・アカウントプロフィールを扱う「アカウントAPI」 ・今現在のツイートを扱う「リアルタイムAPI」 ・DM機能の「ダイレクトメッセージAPI」 ・広告機能と連携できる「広告API」 TwitterAPI制限とは TwitterAPlと合わせてよく聞くのが「API制限」という言葉です。 ツイートなどのサービスを利用するとき、どうしてもTwitterのサーバーに接続必要があります。APIを使わない場合はウェブサイトからサーバーに投稿を反映させますが、APIを使う場合はプログラムを用いて直接サーバーにツイートを反映させます。 APIを使うと、ウェブサイトとは違って大量にツイートを投稿するなど「悪意ある攻撃」が簡単にできてしまいます。これをしてしまうと、Twitterのサーバーの負荷が高くなり、サービスの提供が不安定になってしまいます。 そこで一定時間あたりにサーバーがリクエスト(投稿などを反映させること)を受け付ける数に上限を設けています。例えば、フォロワーの取得であれば、「1分に1リクエストまで」といった制限があります。これを超えてAPIリクエストを送ってしまうと、アカウントが「有害なもの」をして一時的に制限されてしまいます。これがいわゆる「API制限」を食らった状態です。 Twitter APIでできること ①タイムラインの取得 自分のタイムラインはもちろん、特定ユーザーのつぶやき一覧や、世界中からランダムに取得、地域のツイートなどの取得ができます。 ②ツイートの投稿 APIを用いると、Pythonなどのプログラミング言語などを用いてツイートの投稿ができるようになります。そのため、外部サービスを使わずとも自分で定期的にツイートを行うbotを作ることができます。 ③リツイートやいいねの実行 ツイートだけではなく、リツイートやいいねの実行や取り消しを行うことができます。 これを用いると、自分のツイートを定期的にRTしたり、フォローしているユーザーのツイートをいいねしたりすることができます。 ④フォローやアンフォローの実行 フォロー、アンフォローなどの操作もAPIから行うことができます。 フォロー・アンフォローの操作はAPI制限がツイートに比べて厳しいため、気をつける必要がある。 ⑤ツイートの検索 ツイートの検索は、ユーザーのトレンド予測などに大変役立ちます。ツイート数やツイート内容を分析することもできます。 ※TwitterAPIを使うには申請が必要です。 申請は下記を参考 TwitterAPI一覧 TwitterAPI ✕ PHP Twitter機能のコード集 【PHP】Twitter API(ツイート検索・ツイートする・いいねする・トレンド検索する・APIの制限数を確認する) 【PHP】TwitterのトレンドをAPIから取得する&HTML出力 LaravelでTwitter認証を実装する TwitterAPIを使った面白い機能 就寝中であることをtwitterの名前欄でお知らせ Twitterで抽出したツイートをTL形式にしてサイトに埋め込む Twitterプロフィール画像が自動更新される「うたた寝bot」 TwitterのTLを見せ合えるミニサービス ツイートの性格分析による本のマッチングシステム Twitter 誹謗中傷撃退マシン Twitterのつぶやきをテキストマイニングによって可視化する
- 投稿日:2021-07-15T11:09:37+09:00
PHPのnumber_formatが引数がnullで0を、ブランクでエラーを返してくるのが嫌で、neo_number_formatを書きました
概要 number_formatは引数の値がnullで文字0を返してくる。引数の値がブランクの時はエラーとなる。そのためデータベースの値をそのままWEBフォームに使えず、必ずis_null等のif文が入る事に嫌気が差し、nullならnull、ブランクならブランクを返すneo_number_formatを書きました。 number_formatとの違い 引数の値 number_format戻り値 neo_number_format戻り値 null '0' null ブランク エラー ブランク コード 関数 /** * number_formatはnullで0、ブランクでエラーになりDBの値と相性が悪いため * * number_format(null) → 0 * number_format('') → error * * neo_number_format(null) → null * neo_number_format('') → '' * * @param mixed $num * @return string */ function neo_number_format($num) { if (is_null($num)) { return null; } if ('string' === gettype($num)) { $num = trim(strval($num)); if ('' === $num) { return ''; } } if (! is_numeric($num)) { trigger_error(__FUNCTION__.'(): Argument #1 ($num) must be of type float, string given', E_USER_NOTICE); return ''; } while (1) { $before = $num; $num = preg_replace('/^\+?(-?\d+)(\d{3})/', '$1,$2', $num); if ($before == $num) { break; } } return $num; } テスト 違う動作の確認 引数の値 number_format戻り値 neo_number_format戻り値 null '0' null ブランク エラー ブランク 同じ動作の確認 引数の値 number_formatと同じ結果 0 '0' '0' '0' 1000 '1,000' '+1000' '1,000' -1000 '-1,000' '-1000' '-1,000' '1,000' エラー '-1,000' エラー テストコード public function test_neo_number_format() { // 引数:null 比較 $this->assertSame(null, neo_number_format(null)); $this->assertSame('0', number_format(null)); // 引数:文字列 ブランク 比較 $this->assertSame('', neo_number_format('')); $e = $t = null; try { // 例外発生 number_format(''); } catch (\Exception $e) { $this->assertTrue((bool)preg_match('/^number_format\(\) expects parameter 1 to be (float|double), string given$/', $e->getMessage())); } catch (\TypeError $t) { $this->assertSame('number_format(): Argument #1 ($num) must be of type float, string given', $t->getMessage()); } finally { $this->assertTrue(is_object($e) || is_object($t)); } // 引数:数値 0 同じ動作 $this->assertSame('0', neo_number_format(0)); $this->assertSame('0', number_format(0)); // 引数:文字列 '0' 同じ動作 $this->assertSame('0', neo_number_format('0')); $this->assertSame('0', number_format('0')); // 引数:数値 1000 同じ動作 $this->assertSame('1,000', neo_number_format(1000)); $this->assertSame('1,000', number_format(1000)); // 引数:文字列 '+1000' 同じ動作 $this->assertSame('1,000', neo_number_format('+1000')); $this->assertSame('1,000', number_format('+1000')); // 引数:数値 -1000 同じ動作 $this->assertSame('-1,000', neo_number_format(-1000)); $this->assertSame('-1,000', number_format(-1000)); // 引数:文字列 '-1000' 同じ動作 $this->assertSame('-1,000', neo_number_format('-1000')); $this->assertSame('-1,000', number_format('-1000')); // 引数:文字列 '1,000' 同じ動作 $e = null; try { neo_number_format('1,000'); } catch (\Exception $e) { $this->assertSame('neo_number_format(): Argument #1 ($num) must be of type float, string given', $e->getMessage()); } finally { $this->assertTrue(is_object($e)); } $e = $t = null; try { // 例外発生 number_format('1,000'); } catch (\Exception $e) { $this->assertSame('A non well formed numeric value encountered', $e->getMessage()); } catch (\TypeError $t) { $this->assertSame('number_format(): Argument #1 ($num) must be of type float, string given', $t->getMessage()); } finally { $this->assertTrue(is_object($e) || is_object($t)); } // 引数:文字列 '-1,000' 同じ動作 $e = null; try { neo_number_format('-1,000'); } catch (\Exception $e) { $this->assertSame('neo_number_format(): Argument #1 ($num) must be of type float, string given', $e->getMessage()); } finally { $this->assertTrue(is_object($e)); } $e = $t = null; try { // 例外発生 number_format('-1,000'); } catch (\Exception $e) { $this->assertSame('A non well formed numeric value encountered', $e->getMessage()); } catch (\TypeError $t) { $this->assertSame('number_format(): Argument #1 ($num) must be of type float, string given', $t->getMessage()); } finally { $this->assertTrue(is_object($e) || is_object($t)); } }
- 投稿日:2021-07-15T07:26:16+09:00
PHPで連続アクセスの制限(同一のIPアドレスから等)、DoS攻撃対策、総当り対策、F5アタック対策
概要 同一の識別文字列(例:IPアドレス)から10秒間に10回あれば60秒アクセス制限をする。 同一の識別文字列(例:IPアドレス)から60秒間に30回あれば300秒アクセス制限をする。 DoS攻撃対策、総当り対策、F5アタック対策としてコピペするだけで動作する。(はず。私の環境が環境がXAMPPなので?) 適切な関数名を募集しています。 動作を確認できるサイト やみ姐さん?の家 F5アタックすると確認できます。 処理概要 同一の識別文字列(例:IPアドレス)毎にデータファイルを作成 同一の識別文字列(例:IPアドレス)でアクセスがあるたびに現在のタイムスタンプを履歴に追加 履歴から10秒間のアクセス回数と60秒間のアクセス回数を取得 60秒間に30回あれば300秒アクセス制限 10秒間に10回あれば60秒アクセス制限 ソースコード 使用方法 if (isset($_SERVER) && array_key_exists('REMOTE_ADDR', $_SERVER) && '' !== $_SERVER['REMOTE_ADDR']) { access_is_restricted_according_to_the_number_of_accesses_from_the_same_IP_address_within_a_unit_time($_SERVER['REMOTE_ADDR'], 'ipv4'); } config class config { /** * 10秒以内にn回のアクセスがあった場合アクセス制限をする */ const THRESHOLD_ACCESS_COUNT_WITHIN_10SECONDS = 10; /** * 10秒以内の連続アクセス違反の時のアクセス制限時間 */ const ACCESS_RESTRICTION_PERIOD_10 = '60 second'; /** * 60秒以内にこの値の回数のアクセスがあった時はアクセス制限をする */ const THRESHOLD_ACCESS_COUNT_WITHIN_60SECONDS = 30; /** * 60秒以内の連続アクセス違反の時のアクセス制限時間 */ const ACCESS_RESTRICTION_PERIOD_60 = '300 second'; } function /** * IPアドレスのチェック(CodeIgniterのForm_validationより) * * @param string $ip * @param string $options 'ipv4' or 'ipv6' to validate a specific IP format * @return bool */ function valid_ip($ip, $options = null) { if (! is_null($options)) { $which = strtolower($options); if ('ipv4' === $which) { $options = 1048576; } else if ('ipv6' === $which) { $options = 2097152; } } return (bool)filter_var($ip, 275, $options); } /** * 単位時間内に同じIPアドレスからのアクセス数に応じてアクセス制限をする * ※適切な関数名を考案中 * * @param string $ip_address * @param string $valid_ip_options */ function access_is_restricted_according_to_the_number_of_accesses_from_the_same_IP_address_within_a_unit_time($ip_address, $valid_ip_options) { // localhost用 if ('::1' === $ip_address) { $ip_address = '127.0.0.1'; } if (! valid_ip($ip_address, $valid_ip_options)) { trigger_error(__FUNCTION__.'(): Argument #1 ($ip_address) must be in the form of an IP address.', E_USER_WARNING); return; } access_is_restricted_according_to_the_number_of_accesses_from_the_same_XXXX_within_a_unit_time($ip_address); } /** * 単位時間内に同じ$arg1のアクセス数に応じてアクセス制限をする * ※適切な関数名を考案中 * * @param string $arg1 */ function access_is_restricted_according_to_the_number_of_accesses_from_the_same_XXXX_within_a_unit_time($arg1) { $arg1_type = gettype($arg1); if ('string' !== $arg1_type){ trigger_error(__FUNCTION__.'(): Argument #1 ($arg1) must be of type string.', E_USER_WARNING); return; } if ('' === strval($arg1)) { trigger_error(__FUNCTION__.'(): Argument #1 ($arg1) is empty.', E_USER_WARNING); return; } $access_restriction_message_format = 'アクセス制限中<br>期限:%s'; // ディレクトリ存在チェックと作成とパーミッション設定 $access_history_dir = dirname(__FILE__); // $access_history_dir = 'アプリのtmpディレクトリなど適切なディレクトリ/access_history'; if (! is_dir($access_history_dir)) { if (! @mkdir($access_history_dir, 0777, true)) { return; } } if (! is_writable($access_history_dir)) { @chmod($access_history_dir, 0777); } $access_history_path = $access_history_dir.'/'.str_replace(['.', ':'], '_', $arg1).'.ser'; if (! is_file($access_history_path)) { $access_history_handle = fopen($access_history_path, 'w'); $access_history_flock = flock($access_history_handle, LOCK_EX); $access_history = new \stdClass(); $access_history->times = []; $access_history->access_restriction_period = 0; } else { $access_history_handle = fopen($access_history_path, 'r+'); $access_history_flock = flock($access_history_handle, LOCK_EX); // ファイルに書き込まれている内容から、前回までのアクセス履歴を復元 $access_history = unserialize(fgets($access_history_handle)); if (! is_object($access_history) || ! property_exists($access_history, 'times') || ! is_array($access_history->times) || ! property_exists($access_history, 'access_restriction_period')) { $access_history = new \stdClass(); $access_history->times = []; $access_history->access_restriction_period = 0; } else { // 前回までのアクセス履歴でアクセス制限 if (time() < $access_history->access_restriction_period) { fclose($access_history_handle); exit(sprintf($access_restriction_message_format, date('Y-m-d H:i:s', $access_history->access_restriction_period))); } } } // ファイルロックをできなかった時はここから先は何もやれないのでアクセス履歴を更新する事なく終了 if (! $access_history_flock) { trigger_error(__FUNCTION__.': File lock failed.', E_USER_NOTICE); return; } // アクセス履歴に追加 $access_history->times[] = time(); // 単位時間内の件数 $time_10seconds_ago = strtotime('-10 second'); $time_60seconds_ago = strtotime('-60 second'); $count_within_10seconds = 0; $count_within_60seconds = 0; foreach ($access_history->times as $i => $access_time) { if ($time_60seconds_ago < $access_time) { ++$count_within_60seconds; if ($time_10seconds_ago < $access_time) { ++$count_within_10seconds; } } else { // 古いアクセス履歴の削除 unset($access_history->times[$i]); } } // 添字をリセット $access_history->times = array_values($access_history->times); // アクセス制限期限を設定 $access_history->access_restriction_period = 0; if (config::THRESHOLD_ACCESS_COUNT_WITHIN_60SECONDS < $count_within_60seconds) { $access_history->access_restriction_period = strtotime('+'.config::ACCESS_RESTRICTION_PERIOD_60); } else if (config::THRESHOLD_ACCESS_COUNT_WITHIN_10SECONDS < $count_within_10seconds) { $access_history->access_restriction_period = strtotime('+'.config::ACCESS_RESTRICTION_PERIOD_10); } // アクセス履歴を保存 ftruncate($access_history_handle, 0); rewind($access_history_handle); fputs($access_history_handle, serialize($access_history)); fclose($access_history_handle); // 今回のアクセスをアクセス制限 if (time() < $access_history->access_restriction_period) { exit(sprintf($access_restriction_message_format, date('Y-m-d H:i:s', $access_history->access_restriction_period))); } // 古いアクセス履歴ファイルを削除 mt_srand(); if (0 === mt_rand(0, 1000)) { foreach (glob($access_history_dir.'/*.ser') as $file) { if (filemtime($file) < strtotime('-1 hour')) { unlink($file); } } } }
- 投稿日:2021-07-15T07:26:16+09:00
PHPで同一のIPアドレスから不必要に連続アクセスされた時に一時的にサービス利用制限をかける。総当り対策、F5アタック対策、DoS攻撃対策になるかも
概要 同一の識別文字列(例:IPアドレス)から10秒間に10回あれば60秒アクセス制限をする。 同一の識別文字列(例:IPアドレス)から60秒間に30回あれば300秒アクセス制限をする。 DoS攻撃対策、総当り対策、F5アタック対策としてコピペするだけで動作する。(はず。私の環境が環境がXAMPPなので?) 適切な関数名を募集しています。 ** NOTE ** DoS攻撃などを防ぐには、denyhostsやfail2banなどのミドルウェアやApacheとNginx等WEBサーバーでアクセス制限をしましょう。 本記事はミドルウェアレベルで設定ができない場合のアプリケーションレベルでの制限を目的としています。 動作を確認できるサイト やみ姐さん?の家 F5アタックすると確認できます。 処理概要 同一の識別文字列(例:IPアドレス)毎にデータファイルを作成 同一の識別文字列(例:IPアドレス)でアクセスがあるたびに現在のタイムスタンプを履歴に追加 履歴から10秒間のアクセス回数と60秒間のアクセス回数を取得 60秒間に30回あれば300秒アクセス制限 10秒間に10回あれば60秒アクセス制限 ソースコード 使用方法 if (isset($_SERVER) && array_key_exists('REMOTE_ADDR', $_SERVER) && '' !== $_SERVER['REMOTE_ADDR']) { access_is_restricted_according_to_the_number_of_accesses_from_the_same_IP_address_within_a_unit_time($_SERVER['REMOTE_ADDR'], 'ipv4'); } config class config { /** * 10秒以内にn回のアクセスがあった場合アクセス制限をする */ const THRESHOLD_ACCESS_COUNT_WITHIN_10SECONDS = 10; /** * 10秒以内の連続アクセス違反の時のアクセス制限時間 */ const ACCESS_RESTRICTION_PERIOD_10 = '60 second'; /** * 60秒以内にこの値の回数のアクセスがあった時はアクセス制限をする */ const THRESHOLD_ACCESS_COUNT_WITHIN_60SECONDS = 30; /** * 60秒以内の連続アクセス違反の時のアクセス制限時間 */ const ACCESS_RESTRICTION_PERIOD_60 = '300 second'; } function /** * IPアドレスのチェック(CodeIgniterのForm_validationより) * * @param string $ip * @param string $options 'ipv4' or 'ipv6' to validate a specific IP format * @return bool */ function valid_ip($ip, $options = null) { if (! is_null($options)) { $which = strtolower($options); if ('ipv4' === $which) { $options = 1048576; } else if ('ipv6' === $which) { $options = 2097152; } } return (bool)filter_var($ip, 275, $options); } /** * 単位時間内に同じIPアドレスからのアクセス数に応じてアクセス制限をする * ※適切な関数名を考案中 * * @param string $ip_address * @param string $valid_ip_options */ function access_is_restricted_according_to_the_number_of_accesses_from_the_same_IP_address_within_a_unit_time($ip_address, $valid_ip_options) { // localhost用 if ('::1' === $ip_address) { $ip_address = '127.0.0.1'; } if (! valid_ip($ip_address, $valid_ip_options)) { trigger_error(__FUNCTION__.'(): Argument #1 ($ip_address) must be in the form of an IP address.', E_USER_WARNING); return; } access_is_restricted_according_to_the_number_of_accesses_from_the_same_XXXX_within_a_unit_time($ip_address); } /** * 単位時間内に同じ$arg1のアクセス数に応じてアクセス制限をする * ※適切な関数名を考案中 * * @param string $arg1 */ function access_is_restricted_according_to_the_number_of_accesses_from_the_same_XXXX_within_a_unit_time($arg1) { $arg1_type = gettype($arg1); if ('string' !== $arg1_type){ trigger_error(__FUNCTION__.'(): Argument #1 ($arg1) must be of type string.', E_USER_WARNING); return; } if ('' === strval($arg1)) { trigger_error(__FUNCTION__.'(): Argument #1 ($arg1) is empty.', E_USER_WARNING); return; } $access_restriction_message_format = 'アクセス制限中<br>期限:%s'; // ディレクトリ存在チェックと作成とパーミッション設定 $access_history_dir = dirname(__FILE__); // $access_history_dir = 'アプリのtmpディレクトリなど適切なディレクトリ/access_history'; if (! is_dir($access_history_dir)) { if (! @mkdir($access_history_dir, 0777, true)) { return; } } if (! is_writable($access_history_dir)) { @chmod($access_history_dir, 0777); } $access_history_path = $access_history_dir.'/'.str_replace(['.', ':'], '_', $arg1).'.ser'; if (! is_file($access_history_path)) { $access_history_handle = fopen($access_history_path, 'w'); $access_history_flock = flock($access_history_handle, LOCK_EX); $access_history = new \stdClass(); $access_history->times = []; $access_history->access_restriction_period = 0; } else { $access_history_handle = fopen($access_history_path, 'r+'); $access_history_flock = flock($access_history_handle, LOCK_EX); // ファイルに書き込まれている内容から、前回までのアクセス履歴を復元 $access_history = unserialize(fgets($access_history_handle)); if (! is_object($access_history) || ! property_exists($access_history, 'times') || ! is_array($access_history->times) || ! property_exists($access_history, 'access_restriction_period')) { $access_history = new \stdClass(); $access_history->times = []; $access_history->access_restriction_period = 0; } else { // 前回までのアクセス履歴でアクセス制限 if (time() < $access_history->access_restriction_period) { fclose($access_history_handle); exit(sprintf($access_restriction_message_format, date('Y-m-d H:i:s', $access_history->access_restriction_period))); } } } // ファイルロックをできなかった時はここから先は何もやれないのでアクセス履歴を更新する事なく終了 if (! $access_history_flock) { trigger_error(__FUNCTION__.': File lock failed.', E_USER_NOTICE); return; } // アクセス履歴に追加 $access_history->times[] = time(); // 単位時間内の件数 $time_10seconds_ago = strtotime('-10 second'); $time_60seconds_ago = strtotime('-60 second'); $count_within_10seconds = 0; $count_within_60seconds = 0; foreach ($access_history->times as $i => $access_time) { if ($time_60seconds_ago < $access_time) { ++$count_within_60seconds; if ($time_10seconds_ago < $access_time) { ++$count_within_10seconds; } } else { // 古いアクセス履歴の削除 unset($access_history->times[$i]); } } // 添字をリセット $access_history->times = array_values($access_history->times); // アクセス制限期限を設定 $access_history->access_restriction_period = 0; if (config::THRESHOLD_ACCESS_COUNT_WITHIN_60SECONDS < $count_within_60seconds) { $access_history->access_restriction_period = strtotime('+'.config::ACCESS_RESTRICTION_PERIOD_60); } else if (config::THRESHOLD_ACCESS_COUNT_WITHIN_10SECONDS < $count_within_10seconds) { $access_history->access_restriction_period = strtotime('+'.config::ACCESS_RESTRICTION_PERIOD_10); } // アクセス履歴を保存 ftruncate($access_history_handle, 0); rewind($access_history_handle); fputs($access_history_handle, serialize($access_history)); fclose($access_history_handle); // 今回のアクセスをアクセス制限 if (time() < $access_history->access_restriction_period) { exit(sprintf($access_restriction_message_format, date('Y-m-d H:i:s', $access_history->access_restriction_period))); } // 古いアクセス履歴ファイルを削除 mt_srand(); if (0 === mt_rand(0, 1000)) { foreach (glob($access_history_dir.'/*.ser') as $file) { if (filemtime($file) < strtotime('-1 hour')) { unlink($file); } } } }
- 投稿日:2021-07-15T07:26:16+09:00
PHPで連続アクセスの制限(同一のIPアドレスから等)総当り対策、F5アタック対策、DoS攻撃対策?
概要 同一の識別文字列(例:IPアドレス)から10秒間に10回あれば60秒アクセス制限をする。 同一の識別文字列(例:IPアドレス)から60秒間に30回あれば300秒アクセス制限をする。 DoS攻撃対策、総当り対策、F5アタック対策としてコピペするだけで動作する。(はず。私の環境が環境がXAMPPなので?) 適切な関数名を募集しています。 ** NOTE ** DoS攻撃などを防ぐには、denyhostsやfail2banなどのミドルウェアやApacheとNginx等WEBサーバーでアクセス制限をしましょう。 本記事はミドルウェアレベルで設定ができない場合のアプリケーションレベルでの制限を目的としています。 同一の識別文字列という事で、同じログインユーザーを使用し別々の場所(IP)から同時に過度なアクセスがあった時に、ログインユーザーに一時的な利用制限をするなどを想定しています。 …というとユーザーマスタに項目を持たせた方が適切ですよね?という話が出てくるかもしれませんが、その通りだと思います。ただ、ユーザーコードに限定しているわけでもIPアドレスに限定しているわけでもなく「例え」ですので、アプリケーションの設計の話はご容赦くださいませ。 動作を確認できるサイト やみ姐さん?の家 F5アタックすると確認できます。 処理概要 同一の識別文字列(例:IPアドレス)毎にデータファイルを作成 同一の識別文字列(例:IPアドレス)でアクセスがあるたびに現在のタイムスタンプを履歴に追加 履歴から10秒間のアクセス回数と60秒間のアクセス回数を取得 60秒間に30回あれば300秒アクセス制限 10秒間に10回あれば60秒アクセス制限 ソースコード 使用方法 if (isset($_SERVER) && array_key_exists('REMOTE_ADDR', $_SERVER) && '' !== $_SERVER['REMOTE_ADDR']) { access_is_restricted_according_to_the_number_of_accesses_from_the_same_IP_address_within_a_unit_time($_SERVER['REMOTE_ADDR'], 'ipv4'); } config class config { /** * 10秒以内にn回のアクセスがあった場合アクセス制限をする */ const THRESHOLD_ACCESS_COUNT_WITHIN_10SECONDS = 10; /** * 10秒以内の連続アクセス違反の時のアクセス制限時間 */ const ACCESS_RESTRICTION_PERIOD_10 = '60 second'; /** * 60秒以内にこの値の回数のアクセスがあった時はアクセス制限をする */ const THRESHOLD_ACCESS_COUNT_WITHIN_60SECONDS = 30; /** * 60秒以内の連続アクセス違反の時のアクセス制限時間 */ const ACCESS_RESTRICTION_PERIOD_60 = '300 second'; /** * 制限をかけないIPアドレス一覧 */ const ACCESS_RESTRICTION_ALLOWED_IPS = [ '127.0.0.1' => null, '192.168.100.1' => null, ]; } function /** * IPアドレスのチェック(CodeIgniterのForm_validationより) * * @param string $ip * @param string $options 'ipv4' or 'ipv6' to validate a specific IP format * @return bool */ function valid_ip($ip, $options = null) { if (! is_null($options)) { $which = strtolower($options); if ('ipv4' === $which) { $options = 1048576; } else if ('ipv6' === $which) { $options = 2097152; } } return (bool)filter_var($ip, 275, $options); } /** * 単位時間内に同じIPアドレスからのアクセス数に応じてアクセス制限をする * ※適切な関数名を考案中 * * @param string $ip_address * @param string $valid_ip_options */ function access_is_restricted_according_to_the_number_of_accesses_from_the_same_IP_address_within_a_unit_time($ip_address, $valid_ip_options) { // localhost接続 if ('::1' === $ip_address) { $ip_address = '127.0.0.1'; } // 例外 if (array_key_exists($ip_address, config::ACCESS_RESTRICTION_ALLOWED_IPS)) { return; } if (! valid_ip($ip_address, $valid_ip_options)) { trigger_error(__FUNCTION__.'(): Argument #1 ($ip_address) must be in the form of an IP address.', E_USER_WARNING); return; } access_is_restricted_according_to_the_number_of_accesses_from_the_same_XXXX_within_a_unit_time($ip_address); } /** * 単位時間内に同じ$arg1のアクセス数に応じてアクセス制限をする * ※適切な関数名を考案中 * * @param string $arg1 */ function access_is_restricted_according_to_the_number_of_accesses_from_the_same_XXXX_within_a_unit_time($arg1) { $arg1_type = gettype($arg1); if ('string' !== $arg1_type){ trigger_error(__FUNCTION__.'(): Argument #1 ($arg1) must be of type string.', E_USER_WARNING); return; } if ('' === strval($arg1)) { trigger_error(__FUNCTION__.'(): Argument #1 ($arg1) is empty.', E_USER_WARNING); return; } $access_restriction_message_format = 'アクセス制限中<br>期限:%s'; // ディレクトリ存在チェックと作成とパーミッション設定 $access_history_dir = dirname(__FILE__); // $access_history_dir = 'アプリのtmpディレクトリなど適切なディレクトリ/access_history'; if (! is_dir($access_history_dir)) { if (! @mkdir($access_history_dir, 0777, true)) { return; } } if (! is_writable($access_history_dir)) { @chmod($access_history_dir, 0777); } $access_history_path = $access_history_dir.'/'.str_replace(['.', ':'], '_', $arg1).'.ser'; if (! is_file($access_history_path)) { $access_history_handle = fopen($access_history_path, 'w'); $access_history_flock = flock($access_history_handle, LOCK_EX); $access_history = new \stdClass(); $access_history->times = []; $access_history->access_restriction_period = 0; } else { $access_history_handle = fopen($access_history_path, 'r+'); $access_history_flock = flock($access_history_handle, LOCK_EX); // ファイルに書き込まれている内容から、前回までのアクセス履歴を復元 $access_history = unserialize(fgets($access_history_handle)); if (! is_object($access_history) || ! property_exists($access_history, 'times') || ! is_array($access_history->times) || ! property_exists($access_history, 'access_restriction_period')) { $access_history = new \stdClass(); $access_history->times = []; $access_history->access_restriction_period = 0; } else { // 前回までのアクセス履歴でアクセス制限 if (time() < $access_history->access_restriction_period) { fclose($access_history_handle); exit(sprintf($access_restriction_message_format, date('Y-m-d H:i:s', $access_history->access_restriction_period))); } } } // ファイルロックをできなかった時はここから先は何もやれないのでアクセス履歴を更新する事なく終了 if (! $access_history_flock) { trigger_error(__FUNCTION__.': File lock failed.', E_USER_NOTICE); return; } // アクセス履歴に追加 $access_history->times[] = time(); // 単位時間内の件数 $time_10seconds_ago = strtotime('-10 second'); $time_60seconds_ago = strtotime('-60 second'); $count_within_10seconds = 0; $count_within_60seconds = 0; foreach ($access_history->times as $i => $access_time) { if ($time_60seconds_ago < $access_time) { ++$count_within_60seconds; if ($time_10seconds_ago < $access_time) { ++$count_within_10seconds; } } else { // 古いアクセス履歴の削除 unset($access_history->times[$i]); } } // 添字をリセット $access_history->times = array_values($access_history->times); // アクセス制限期限を設定 $access_history->access_restriction_period = 0; if (config::THRESHOLD_ACCESS_COUNT_WITHIN_60SECONDS < $count_within_60seconds) { $access_history->access_restriction_period = strtotime('+'.config::ACCESS_RESTRICTION_PERIOD_60); } else if (config::THRESHOLD_ACCESS_COUNT_WITHIN_10SECONDS < $count_within_10seconds) { $access_history->access_restriction_period = strtotime('+'.config::ACCESS_RESTRICTION_PERIOD_10); } // アクセス履歴を保存 ftruncate($access_history_handle, 0); rewind($access_history_handle); fputs($access_history_handle, serialize($access_history)); fclose($access_history_handle); // 今回のアクセスをアクセス制限 if (time() < $access_history->access_restriction_period) { exit(sprintf($access_restriction_message_format, date('Y-m-d H:i:s', $access_history->access_restriction_period))); } // 古いアクセス履歴ファイルを削除 mt_srand(); if (0 === mt_rand(0, 1000)) { foreach (glob($access_history_dir.'/*.ser') as $file) { if (filemtime($file) < strtotime('-1 hour')) { unlink($file); } } } }
- 投稿日:2021-07-15T00:30:41+09:00
PHP Laravel Artisanコマンドのメモ
はじめに Laravelの開発で色々なArtisanコマンドがあるため、よく使用するものを、 ここにメモとして残して、追加で必要なものは更新していこうと思う Artisanコマンドとは PHPのフレームワークであるLaravelのコマンドのこと Artisanコマンドを使用することでModelやControllerを作成することができる Artisanでのコマンドリスト ターミナル php artisan list Artisanでのコマンドのリストをターミナルに表示できる サーバの起動コマンド cd コマンドでLaravelのプロジェクトがあるとこまで移動しておくこと ターミナル php artisan serve 記載されているURL:http://〇〇〇.〇〇〇.〇〇〇.〇〇〇:8000 このURLにアクセスすればサーバーが立ち上がっているか確認できる 画像コマンドのミスがありますが見逃してください・・・ tinker起動 Laravelの機能を試せるREPLが起動します。 ターミナル php artisan tinker tinker終了コマンド ターミナル >>> exit Exit: Goodbye マイグレーション マイグレーションファイルの作成 マイグレーションファイルとは、Laravelからデータベースのテーブルを管理 するためのファイルのこと ターミナル php artisan make:migration create_messages_table --create=messages php artisan make:migration //マイグレーションファイル作成のためのコマンド create_messages_table //マイグレーションファイルの名前 名前を見て何をするかわかる名前にする --create=messages //テーブル名を決定 --create=messages ← すべて小文字で複数形 マイグレーションのロールバックのコマンド マイグレーションファイルをある地点まで戻すコマンド ターミナル php artisan migrate:rollback マイグレーションの実行 ターミナル php artisan migrate マイグレーションの実行状況の確認 ターミナル php artisan migrate:status モデルの作成 Modelはapp/ 直下に生成、配置される ターミナル php artisan make:model モデル名 ← 頭が大文字で単数形であること! Controllerの生成 ターミナル php artisan make:controller コントローラ名 RESTful Resource Controllerの生成 例文のルーティング routes/web.php <?php Route::resource('messages', 'MessagesController'); この時のコントローラの生成コマンド Routerに対応したController名をつけないとルーティングされないので注意 ターミナル php artisan make:controller MessagesController --resource 参考にさせていただいたサイト 【Laravel】Artisanコマンド基礎