- 投稿日:2019-10-04T23:49:37+09:00
Mysql:AutoIncrementIdを推測するワザ
概要
バイナリサーチでAIDを推測するだけ
ありがちな遅いクエリ
SELECT * FROM `sugoi_table` WHERE `create_time` >= '2019-10-01' (48,126 合計, クエリの実行時間 28.3798 秒)Explainの結果
id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE sugoi_table range PRIMARY PRIMARY 4 NULL 32715643 Using where rows(走査範囲)が多すぎる!!
- MySQLは「1番目のレコードから全てのレコードのcreate_timeが10/1より大きいか?」を判断するので大変遅い
- 本番write用のDBにこんなもん流した日にはしばらくサービスが反応しなくなる
- せめて迂闊なselectを投げる前にindexが効いてるか確認しよう・・・
シンプルな解決方法
AIDで走査範囲を絞る
SELECT * FROM `sugoi_table` WHERE `user_id` >=50000000 AND `create_time` >= '2019-10-01' (48,153 合計, クエリの実行時間 0.0621 秒)早くなった
Explainの結果
id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE sugoi_table range PRIMARY PRIMARY 4 NULL 500844 Using where rowsが3200万→50万に減ったのが要因
- 適当に作った調査クエリを流して「結果が返ってこない・・・」とかなりそうな時は事前に使おう
だがしかし、それっぽいAIDを調べるのが面倒くさい!!
crate_timeで二分探索(バイナリサーチ)すればいい
アルゴリズムを勉強するなら二分探索から始めよう! 『なっとく!アルゴリズム』より
文字が読みたくない人向け
- 全体の中心のデータを見て「大きい」なら後ろ、「小さい」なら前を繰り返して検索する方法
- 計算量はO(log2 n)だそうな
実装してみる
/** * 指定テーブルの指定日付のAIDを推測する */ public static function guessAid( $tableName, $targetKey, $targetDateTime) { //まず最大のAIDを取得する $maxId = self::_getMaxAid($tableName, $targetKey); $low = 1; $high = $maxId; $loopCnt = 0; $guess = 0; while ( $low <= $high ) { $loopCnt++; $mid = (int)(($high + $low) / 2); //中央値を作成 floatにならないように $sql = "SELECT create_time FROM {$tableName} WHERE {$targetKey} >= {$mid} LIMIT 1"; $tmpData = Db::query($sql); if ( empty($tmpData)) { //データが無い時の処理(投げやり); break; } if (strtotime($tmpData['create_time']) < strtotime($targetDateTime)) { //時間が小さい時に推測候補を保持しておく //逆条件の時にループが終わると指定時間ちょい後のAIDになってしまう場合がある為 $guess = $low; $low = $mid + 1; } else { $high = $mid - 1; } //データが消されている可能性も考慮して一定ループで抜ける if ( $loopCnt >= 50 ) { break; } } //echo("だいたいこの辺じゃろ"); $id = $guess; return $id; } /** * テーブルのMAX_AIDをとる */ private static function _getMaxAid( $tableName="", $targetKey="") { $sql = "SELECT MAX({$targetKey}) FROM {$tableName}"; $data = Db::query($sql); return $data; }
- 投稿日:2019-10-04T22:11:46+09:00
CakePHP3の嵌りポイント
CakePHPは簡単かつウェブサイトが楽に作れる大変ありがたいフレームワークですが、たまに嵌りポイントがあります。
使っていてハマった点を忘れないように纏めます。
No.1 - patchEntity, save等で孫テーブルがうまく動かない(マージ/保存できない)
親-子-孫の関係になっているテーブルに対し、hasOne/hasManyも定義しているのに親テーブル追加/更新で孫まで上手く反映されない。子まではうまく動く、get/findもうまく動く、エラーは出ない。という場合。
例: Companies --(hasMany)--> Users --(hasMany)--> UserSettings可能性① associationの書き方が間違ってる
get()とは違い、ネストした中にもassociatedを再度書く必要がある。
#get()の場合 $query = $companies->get($id, [ 'contain' => [ 'Users' => ['UserSettings'] ] ]); #newEntity/patchEntityの場合 $company = $companies->newEntity($data, [ 'associated' => [ 'Users' => [ 'associated' => ['UserSettings'] // ↑ここにも'associated'が必要 ] ] ]);*公式マニュアルにも書いてあるものの、まさかget/findと違うことはないだろと思って読み飛ばしがち
*参考にさせてもらったページ(https://qiita.com/zaramme/items/719f77480c5f0cde0ae1)可能性② Entityの$_accessibleに子/孫要素が入ってない
この指定がtrueになっていないと無視される。
bin bakeで自動で設定してくれるはずなものの、条件によっては?親側のEntityに書いてくれていない事があるようで、bakeしなおすか手動で追加が必要。
(普通にget/findしてアクセスするときと同様、hasOneなら単数形、hasManyなら複数形で子テーブルの名前を追加)可能性③ dirtyフラグが立ってない
newEntityやpatchEntityでエンティティ作成後、手動で何か追加/変更した場合、dirty()も呼ばないと更新対象にならない。(公式)
$company->author->name = 'Master Chef'; $company->dirty('author', true);
No.2 - 更新処理で子/孫テーブルのデータが作り直される(delete&insertされる)
例: Users --(hasMany)--> UserSettings
親テーブル更新時に子テーブルのデータもまとめて更新したいが、親はupdate処理されるものの、子テーブルのデータがdeleteで一回消され、insertで新規保存されてしまう場合。
updateでないため子テーブルのprimary keyが変わってしまい、他との関係性が壊れる。可能性: 子/孫テーブルのprimary keyが渡されてない
Entityにprimary keyが無い場合は入れ直し、有る場合はupdate処理になる。
No.3 - 単数/複数形の規約で混乱する
単数/複数の変形
規約で使い分けた気持ちは分かるけど不規則の場合はどうする?
実際のCakePHPの変換を出してくれるサイトを参照。(古い?)
最新Verで動いているcakePHP公式のサイトもあり。
pokemonは単複同型です。DBからcontainで纏めて取ってきた子/孫テーブルの形は?
例: Companies --(hasMany)--> Users --(hasOne)--> Addressies
hasOne, belongsToは単数形、そのまま要素にアクセスできる。
$user->address->street
←addressと単数形になるhasMany, belongsToManyは複数形で配列。
$company->users[0]->name
- 投稿日:2019-10-04T20:19:28+09:00
Ajaxの説明とJQueryによる簡易実装
初めに
前の座席の同僚に「お前、Ajaxって知ってる?」と言われたことが悲しくて執筆に至ります。
本記事はAjaxに関する簡単な説明と処理の実装を目的としています。Ajaxとは
Ajaxとは、「Asynchronous JavaScript + XML」の略です。
直訳すると、「非同期のJavaScriptとXML」となります。
そもそも非同期とは何のことでしょうか。同期通信と非同期通信
まず初めにブラウザとサーバのHTTP通信の基本的な流れについて説明します。
- ブラウザ:サーバに対してリクエストを送信
例:Google検索窓に「小松菜奈」と入力してクリック- サーバ:ブラウザからのリクエストを処理
例:「小松菜奈」に関するデータを抽出し、良い具合に加工- サーバ:2の処理完了後、ブラウザにレスポンスを送信
例:加工済み「小松菜奈」データを送信- ブラウザはレスポンスに基づき画面を描画す 例:「小松菜奈」に関する検索結果が表示
以上のように、ブラウザとサーバ間ではリクエストとレスポンスのやり取りがされます。
1~4を一連の流れとしたとき、
4まで完了しないと1の操作が不可能なものが同期処理、
4まで完了していなくても(任意のタイミングで)1の操作が可能なものが非同期処理
です。より簡単に言うと、
・同期通信:通信が完了するまで次の処理を行うことができない通信、
・非同期通信:通信の途中でも他の処理を行うことができる通信
となります。XML
ところで「Asynchronous JavaScript + XML」のXMLとは一体何のことでしょうか。
XMLは、「Extensible Markup Language」の略で、文章の意味や内容、構造をタグを用いて整理するマークアップ言語の一つです。他の代表的なマークアップ言語にはHTMLがあります。
Webページの見た目を整えるHTMLに対し、XMLは、機械に対して情報を伝達するのに長けていると言われています。
また、W3C(World Wide Web Consortium)によるオープンな規格であることから、多くのシステムに対する汎用性もあり、注目度の高い言語となっています。ここまでのまとめ
Ajaxとは、
1. JavaScriptを用いて、
2. XML形式のデータを、
3. 非同期に通信する
技術だと理解しておけばよいでしょう。Ajaxの実装例
環境
- macOS High Sierra 10.13.6
- JQuery 3.4.1
- PHP 7.1.16
それではAjaxを用いた簡単な処理を実装していきます。
まずはHTMLファイルから。index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <!-- 今回はJQueryを用いてAjax通信を実現するため、GoogleのCDN経由でJQueryを読み込む --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <title>世界一美しいのは誰か</title> </head> <body> <form method="post"> <p>世界で一番美しい人物の名前を答えなさい</p> <p>あなたの答え:<input type="text" name="answer" id="answer"></p> </form> <button id="ajax">Ajax通信</button> <div class="result"></div> <script> $(function(){ // 「Ajax通信」ボタンをクリックしたら発動 $('#ajax').on('click',function(){ $.ajax({ url:'./nana.php', type:'POST', data:{ 'answer':$('#answer').val() } }) // Ajax通信が成功したら発動 .done( (data) => { $('.result').html(data); }) // Ajax通信が失敗したら発動 .fail( (jqXHR, textStatus, errorThrown) => { alert('Ajax通信に失敗しました。'); console.log("jqXHR : " + jqXHR.status); // HTTPステータスを表示 console.log("textStatus : " + textStatus); // タイムアウト、パースエラーなどのエラー情報を表示 console.log("errorThrown : " + errorThrown.message); // 例外情報を表示 }) // Ajax通信が成功・失敗のどちらでも発動 .always( (data) => { if($('#answer').val() == '小松菜奈'){ console.log('あなたは正しい'); }else{ console.log('あなたは間違っている'); } }); }); }); </script> </body> </html>ソースコードのコメントをお読みいただくと理解が深まるかと思います。
console.logはよくデバッグに用いられるので、開発中にどの処理を通過しているのかを探るときに便利です。次にPHPファイルです。
nana.php<?php header('Content-type: text/plain; charset= UTF-8'); if(!empty($_POST['answer'])){ $answer = $_POST['answer']; if($answer == '小松菜奈'){ $result = $answer."は世界一美しい"; }else{ $result = $answer."は世界一美しいわけではない"; } echo $result; }else{ echo '文字を入力してください'; } ?>中身の処理はシンプルで、Ajaxで送信されたinput要素の値を$_POST['answer']で取得し、その値によって返すデータを変えています。
全体の流れ
ローカルサーバとしてPHPのビルトインサーバを用いました。
PHPがインストールされていれば、php -S localhost:ポート番号
で立てることができます。
以下、通信が成功した例を示します。
1. ~/index.htmlをブラウザで表示すると以下のようになる。
2. フォームに文字を入力し「Ajax通信」ボタンを押下。
ここで、入力した値がブラウザからサーバへ送信される。
3. 送信された値がnana.phpファイル内で処理され、加工された値が返却される。
※このnana.phpファイルはサーバ内に存在。
4. 返却された値をブラウザが受け取り、.doneの処理を実行し、描画。
また、上図のようにコンソールに出力がある。これは、通信成功と失敗に関わらず以下の処理が実行されたためである。
上記の1~4の手順において、ページの切り替えが生じないこともAjaxの利便性としてよく挙げられる。
終わりに
浅学ながらフロント開発には欠かせないAjaxの基本について執筆させていただきました。
誤りがありましたらコメントにてご指摘いただけると幸いです。参考文献
- 投稿日:2019-10-04T16:02:45+09:00
PHP5で16進数を符号付10進数に変換する方法
経緯
仕事でPHPで0xから始まる16進数を符号付きの10進数に変換する必要があったが、ネットで探してもなかなか見つからなかったため備忘録として記載。
結論
//16進数を符号付き10進数に変換する private function hex_to_decimal($val){ //0x表記の場合 if(strpos($val,"0x") === 0){ //「0x」の箇所を削除する $hex = substr($val,2); } //先頭文字の1ビット目が1→マイナス if(hexdec(substr($hex,0,1)) & 8 && strlen($hex) === 8){ //2の補数で絶対値を取得 $hex = (hexdec($hex) ^ (16 ** strlen($hex)-1))+1; //マイナスに変換 $dec = 0 - $hex; } else { //先頭文字の1ビット目が0→プラス $dec = hexdec($hex); } return $dec; }解説
符号付16進数はビット列の最初の値で正負の判断をしている。
・先頭ビットが1→負
・先頭ビットが0→正
例として負の値-1(16進数:0xFFFFFFFF)で解説。まずは、下記の箇所
if(hexdec(substr($hex,0,1)) & 8){hexdec(substr($hex,0,1))で16進数の最初の文字「F」の10進数(=15)を取得。
15(2進数で1111)と8(2進数で1000)のビット積を求めることで、最初のビットが1か0かを判定することができる。(&はビット積の意)
最初のビットが1の場合は負の値のため、下記の処理に移る。$hex = (hexdec($hex) ^ (16 ** strlen($hex)-1))+1;ここでは元の16進数(0xFFFFFFFF)と32ビット分全て"1"の数値を排他的論理和でビット演算を行い、その数値にプラス1をすることで2の補数で負の値の絶対値を取得している。
その後に、下記の計算で負の値に変換して終了。
$dec = 0 - $hex;最後に
もっといい方法あったら教えてください!
- 投稿日:2019-10-04T13:55:20+09:00
(めも)PHPで文字数制限をかけて出力する。
- 投稿日:2019-10-04T10:38:26+09:00
strlen()の挙動について
strlen()
事件
本番稼働中のシステムの改修をしてると
以下の記述がありました。
(変数名は実際のものから変更しております。)if (isset($data['content']) && strlen($data['content'] > 0)) { // うんたらかんたら }strlen($data['content'] > 0)えっなにこれ
こわいなにがこわいって本番稼働してるうえに今のところバグとされる報告もないし、
テストしても問題なかったんですよね。挙動を試す
値がある場合
$array = array('content' => 'こんてんと', 'title' => 'たいとる'); echo '正:'; echo strlen($array ['content']) > 0 ? 'true' : 'false'; echo '<br>誤:'; echo strlen($array ['content'] > 0) ? 'true' : 'false';結果
正:true 誤:trueふぇーっ
値がある場合(数値)
$array = array('content' => '10', 'title' => '0'); echo '正:'; echo strlen($array ['content']) > 0 ? 'true' : 'false'; echo '<br>誤:'; echo strlen($array ['content'] > 0) ? 'true' : 'false';結果
正:true 誤:trueうーん。。。
値がない場合
$array = array('content' => '', 'title' => 'たいとる'); echo '正:'; echo strlen($array ['content']) > 0 ? 'true' : 'false'; echo '<br>誤:'; echo strlen($array ['content'] > 0) ? 'true' : 'false';結果
正:false 誤:falseえっいいのこの書き方で。。。?
値が0
$array = array('content' => 0, 'title' => 10); echo '正:'; echo strlen($array ['content']) > 0 ? 'true' : 'false'; echo '<br>誤:'; echo strlen($array ['content'] > 0) ? 'true' : 'false';結果
正:true 誤:falseまあそうなりますわな。
結論
括弧の閉じる位置には気をつけましょう。
今回はUI上数値ではなく文字列が入るか入らないかという作りをしていたのでたまたまよかったようです。
- 投稿日:2019-10-04T09:33:07+09:00
webarenaでubuntu その54
概要
webarenaでubuntu18.04やってみた。
練習問題やってみた。練習問題
いいねボタンを設置せよ。
写真
サンプルコード
<?php header("Access-Control-Allow-Origin: *"); header("Content-Type: application/json"); $id = "test"; $db = new PDO("mysql:host=localhost;dbname=mydb;", 'user', 'pass'); if (!$db) { print "connect error!! <br>"; } else { $sql = "SELECT * FROM counter WHERE id='$id'"; $rs = $db->query($sql); if (!$rs) { print "Error in database!! 0 <br>"; print $sql; } else { $userData = array(); while ($row = $rs->fetch()) { $userData[] = array('count' => $row['cnt']); } echo json_encode($userData); } } ?>成果物
http://embed.plnkr.co/PKwLyPiQP2gHurD0i0pn/
以上。
- 投稿日:2019-10-04T09:19:01+09:00
Laravelを使っていたら、EC-CUBE4系とも仲良くなれた
はじめに
先日、松戸市内で
Laravel
ドキュメントを読む勉強会を開催しました。この時の開催レポートです。当日は松戸駅近くのイベントスペースFANCLUBをお借りしましたー。ありがとうございました。
趣旨がちょっと変わったぞ・・・?
Laravelのドキュメントを原文で読むことで
Laravel
への理解を深めること英語力を向上させることが目的の一石二鳥イベント。ただ、それだけではなくドキュメントを読んでいくうちに、段々と設計思想の話にもなり、非常に学びの多い会となりました。
特に、私が所属しているJoolenには、EC-CUBEのスペシャリストがおり、その方との会話の中での気づきが大きかったのでこちらを中心に書き残しておきます。今回、話題にできたテーマは以下の通りです。1
共通点
.env
を使っていることデータベースへの接続文字列などは
.env
ファイルに持たせることができます。
本番環境では、.env
ファイルではなく環境変数から設定を取得できるので、セキュリティの観点から、その様にしましょうという話で盛り上がりました。
もちろん、APP_ENV
の指定で、参照する.env
ファイルを切り替えることができることも同じです。EC-CUBE4系以降でも使えるテクニックです。
切替え方法が参考になる記事composerを使っていること
共に
composer
を使うことができるので双方ともにパッケージのインストールなどで悩むことはあまりなさそうです。ただ、composer create-project
で雛形を作れることに、EC-CUBE経験者は驚いていました(パッケージをインストールする以外にも機能があったんだ!)DI(Dependency Injection)が使える
EC-CUBE4系もLaravelもDIを使うことができます。ただし、Laravelはコンストラクタだけではなくメソッドでもインジェクションをすることができます。テストがしやすくて良いですね
相違点
まぁ、全く異なるフレームワークなので相違点ばかりなのは当たり前ですが。。。
ルーティング方法
EC-CUBE4系では、
Controller
のアノテーションでルーティングや返すテンプレートを定義します。
EC-CUBEのController
(抜粋)/** * 会員登録画面. * * @Route("/entry", name="entry") * @Template("Entry/index.twig") */ public function index(Request $request) { ... }一方、Laravelではroutes配下の
web.php
など、Routing
は独立しています。<?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('welcome'); })->middleware('auth');コレは双方にとって、少し新鮮だった様です。ちなみに、
Laravel
のルーティングファイルは分割することができます。(質問された)<?php namespace App\Providers; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Route; class RouteServiceProvider extends ServiceProvider { /* * This namespace is applied to your controller routes. * * In addition, it is set as the URL generator's root namespace. * * @var string */ protected $namespace = 'App\Http\Controllers'; /* * Define your route model bindings, pattern filters, etc. * * @return void */ public function boot() { // parent::boot(); } /** * Define the routes for the application. * * @return void */ public function map() { $this->mapApiRoutes(); $this->mapWebRoutes(); // } /* * Define the "web" routes for the application. * * These routes all receive session state, CSRF protection, etc. * * @return void */ protected function mapWebRoutes() { Route::middleware('web') ->namespace($this->namespace) ->group(base_path('routes/web.php')); } /* * Define the "api" routes for the application. * * These routes are typically stateless. * * @return void */ protected function mapApiRoutes() { Route::prefix('api') ->middleware('api') ->namespace($this->namespace) ->group(base_path('routes/api.php')); } }ディレクトリ定義の自由さ
よく言われることですが、
Models
ディレクトリが無いことに驚かれました。ただ、一方でこれはLaravel
を利用する技術者が自らのベストプラクティスを適用できるという意味にもなります。逆にイケてない設計をすると、あとあと苦労するという噂もありますが。。。
EC-CUBEではいわゆる、リポジトリパターンをきっちり採用していますのでLaravel
側でも同じ様な設計をすることで、双方の人材交流はやりやすくなるかなー、と思いました。
Laravelでリポジトリパターン
まぁ、基本的にはその企業やチームの文化やスキルセットに合わせた設計で良いか、というオチでしたが。。。まとめ
EC-CUBE経験者とLaravel経験者で意見交換をすることで、思いも寄らない気づきをたくさん得ることができました。参加してくださった方々、本当にありがとうございました。
pretty urls以降は、時間の都合でざっと眺めた程度になっちゃいました ↩
- 投稿日:2019-10-04T08:47:14+09:00
webarenaでubuntu その53
概要
webarenaでubuntu18.04やってみた。
練習問題やってみた。練習問題
画像アクセスカウンターを表示、せよ。
サンプルコード
<?php $id = "test"; $db = new PDO("mysql:host=localhost;dbname=mydb;", 'user', 'pass'); if (!$db) { print "connect error!! <br>"; } else { $sql = "UPDATE counter SET cnt = cnt + 1 WHERE id='$id'"; $db->query($sql); $sql = "SELECT * FROM counter WHERE id='$id'"; $rs = $db->query($sql); if (!$rs) { print "Error in database!! 0 <br>"; print $sql; } else { $count = 0; while ($row = $rs->fetch()) { $count = $row['cnt']; } header('Content-Type: image/gif'); $im = imagecreatetruecolor(200, 50); $color = imagecolorallocate($im, 255, 255, 255); imagestring($im, 5, 10, 10, $count, $color); imagegif($im); imagedestroy($im); } } ?>成果物
https://embed.plnkr.co/PITdnKLapRfpgIy4juzc/
以上。
- 投稿日:2019-10-04T02:10:11+09:00
PSR-12 への対応状況
PHP-FIG というPHPフレームワークなどの開発者のグループが公開している PHP Standards Recommendations (PSR) の
PSR-12: Extended Coding Style が 2019/08/10(土) に承認されました。
これにより2012年からあった PSR-2: Coding Style Guide は非推奨となっています。PSR はPHP公式で策定されているものではないので、PSR-12 や PSR-2 は必ず守らなければならないコーディング規約というものではありませんが、有名どころのフレームワーク・パッケージで採用されており自身のプロジェクトで採用するメリットはあると思います。
参考: PSRの誤解 - QiitaPSR-12 を採用するためのツールなどの対応状況をまとめます。
(随時追記予定)IDE
名前 対応バージョン リンク PhpStorm - PhpStorm 2019.3 Early Access Program is Now Open - PhpStorm Blog Code Quality
名前 対応バージョン リンク PHP Coding Standards Fixer - PSR-12 Support · Issue #4502 · FriendsOfPHP/PHP-CS-Fixer PHP_CodeSniffer 3.5.0