sidewalkcafe blog

日々是好日

やってて良かったSQLite

範囲呼び出しの話
SQliteには BETWEEN というそのままな呼び出しコマンドがあった

select * from bbs where id BETWEEN 1 and 20

別にwhereで囲んでも結果は同じなんだけど、どっちが早いんだろ?

select * from bbs where id > 1 and id < 20


親記事の数を数える話
もとはtree.logの行数をcount()してたけど
sqlの場合、count(*)を使う

select count(*) from bbs where parent > 0

値の取得はforeachじゃなくて、1行取得のfetchを使うらしい

$query->fetchColumn();


レスコメント表示の話
DSP_RESで一覧画面のレス表示件数、DSP_RESIMGでレス内の画像表示件数が指示されてるので
レス取得のクエリに limitをつける

select * from bbs where tree == $res limit DSP_RES

ただ、クエリ内で定数が使えなかったので変数に格納して呼び出す


画像表示件数は画像をカウントして指定数になるとbreakする

$c_img = (DSP_RES)? 0:null;
if($filename) $c_img ++
if($c_img >= DSP_RES)break;

という訳で三項演算子
ifより少しばかり遅いけど記載が短くなるから好き


って三項演算子の速度比較みてたら

三項演算子よりも暗黙の型変換のほうが、速度低下が大きい

なんですとー!!

$a = '0';
$a ++

こういう記載よくやるけど駄目なんか

POTI-boardのDB化2

SQLiteからデータを呼び出す場合
1.まとめて呼び出して処理しながら配列に入れる
2.その都度必要なデータを呼び出して処理する


どっちの方が早いのかなと
まあ、大量データを扱うなら、PHPで配列操作するよりsqlで個々に呼び出した方が早い気がする


という事で
呼び出し1

<?php
try {
	//db接続
	$db = new PDO('sqlite:log/boardlog.db' );
	
	//ページ数
	$p=0;
	//1ページの記事数
	$page_def = 20;
	//現在ページの記事範囲
	$page_r = $p * $page_def ;
	//ページおわり
	$page_e = $page_r + $page_def ;
	
	//計測開始
	$start = microtime(true);
	//100回繰り返す
	for($i = 0; $i < 100; $i++) {
	//dql呼び出し文
	$sql = "select * from oekakibbs where invz == 0 order by parent asc, tree desc, id asc";
	
	//メインデータ変数
	$dat = array();
	//データ分割
	foreach ($db->query($sql) as $data) {
		//親データ取得
		$parent = $data['parent'];
		//ページおわり
		if($parent > $page_e){ break;}
		//現在ページ迄処理を飛ばす
		if ($parent == 0 ){ $comm_res[] = $data ; continue; }
		if ($parent <= $page_r ){continue ;}
		//親データ格納
		$dat['oya'][] = $data ;
		
		//親記事番号
		$oya = $parent -1;
		//子データ格納
		foreach ($comm_res as $res) {
			if($res['tree'] != $data['tree']){continue;}
			$dat['oya'][$oya][] = $res ; 
		}
	}
	}
	
	$end = microtime(true);
echo "処理時間:" . ($end - $start) . "秒";

	//db切断
    $db = null;
  } catch (PDOException $e) {
    echo "<p>エラー:", $e->getMessage(), "</p>";
}

約5.3病でした


その2

<?php

try {
	//db接続
	$db = new PDO('sqlite:log/boardlog.db' );
	
	
	//ページ数
	$p=0;
	//1ページの記事数
	$page_def = 20;
	//現在ページの記事範囲
	$page_r = $p * $page_def ;
	//ページおわり
	$page_e = $page_r + $page_def ;
	
	
	//計測開始
	$start = microtime(true);
	
	for($i = 0; $i < 100; $i++) {
	//dql呼び出し文
	$sql = "select * from oekakibbs where invz == 0 and parent > 0 order by parent asc";
	
	//メインデータ変数
	$dat = array();
	//データ分割
	foreach ($db->query($sql) as $data) {
	
		//親データ取得
		$parent = $data['parent'];
		//ページおわり
		if($parent > $page_e){ break;}
		//現在ページ迄処理を飛ばす
		if ($parent <= $page_r ){continue ;}
		
		$dat['oya'][] = $data ;
		
		//親記事
		$oya = $parent -1;
		
		$tree_res = $data['tree']; 
		
		$sql2 = "select * from oekakibbs where invz == 0 and tree == $tree_res order by id desc";
		
		foreach ($db->query($sql2) as $res) {
			$dat['oya'][$oya][] = $res ; 
		}
	}
	}
	$end = microtime(true);
echo "処理時間:" . ($end - $start) . "秒";

	//db切断
    $db = null;
  } catch (PDOException $e) {
    echo "<p>エラー:", $e->getMessage(), "</p>";
}

約0.4秒!!


全然違うね〜
やっぱり毎度sqlで呼び出した方が早い


よく考えたらwhere で parent > 開始NO and parent <=終了no とした方が早いか

POTI-boardの警告エラー修正

POTI-boardで良くあるエラーが

Notice: Undefined variable: 変数 in 〜
//定義してない変数を使う

Notice: Undefined offset: 配列のキー in
//定義してない配列の値を使う

Notice: Undefined index: 連想配列 in
//定義してない連想配列を使う

エラーや警告が出ると(サーバー上で表示されなくても)速度がめっちゃ落ちるので
細かく改善していった方が良い


なので
例えばアップロードされるファイルの有る無しは

if(isset($_FILES["upfile"])){
	$upfile_name=$_FILES["upfile"]["name"];
	$upfile=$_FILES["upfile"]["tmp_name"];
}

で取得する


他にも

switch($mode){}
の前に
if (isset($mode)){}
を入れて囲む


関数の場合、参照渡しとデフォルト引数を設定することで呼び出しもとの変数が空でも警告がでなくなる

例えば記事上書きの場合
case 'rewrite':
	rewrite($no,$name,$email,$sub,$com,$url,$pwd,$admin);
	break;

読者編集だと$adminが設定されてないので
関数の方に「&$admin=""」と入れて空の場合でも変数宣言できるようにする

function rewrite($no,$name,$email,$sub,$com,$url,$pwd,&$admin =''){}

これで$adminがあればその値に、なければ空の値が入る

単純に
if($a){
で警告が出てる場合は
if(isset($a) && $a){
としても良い

POTI-boardのDB化1

POTI-boardのログファイルをデータベースに書き込み

こんな幹事のテーブルをつくる

id:コメントid
no:記事no(あとでidと結合削除)
date:日時
name:投稿者名
mail:メールアドレス(or sage)
subject:題名
comment:コメント
url:URL
host:接続ホスト名(ipアドレス)
exid:ユーザーID
pwd:パスワード
ext:画像ファイルの拡張子
img_w:画像幅
img_h:画像高
filename:画像ファイル名
thumbnail:サムネファイル名
cdt:チェックデジット(必要?)
time:お絵描き時間
fontcolor:文字色
tree:親ツリー番号(親はid==treeになる)
parent:親表示順(子はparent=0になる)
invz:記事非表示(invz=1のコメントは表示しない)

img.logの内容に、tree,parent,invzを追加する
tree,parentは、tree.logの内容を1つにまとめるためのもの
invzは削除した記事を表示させないためのもの
(補足)通常、読者が消したコメントを保存しておくことは色々と危うい
ただ、荒らしを通報する場合にipやコメント内容は記録してた方が良いので、管理人削除の場合invz=1として一般からは見えない様にする


ログ呼び出し、テーブル作成、挿入

<?php
//ログファイルの設定
//掲示板のconfigから取って来たのでまわりくどいw
define('LOGFILE', './log/img.log');
define('TREEFILE', './log/tree.log');

//ちゃんと動いてるか確認するための表示
echo("データ更新中");

//データを入れておくための変数
$arrayDATA = array();

//log読み込み
$lines = file(LOGFILE, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES);
$treelines = file(TREEFILE, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES);

//logを各行毎に読み込み
foreach ($lines as $line) {
	//','で分割、それぞれ変数に格納
	list($no, $now, $name, $email, $sub, $com, $url,
		$host, $pwd, $ext, $w, $h, $time, $chk, $ptime, $fcolor)
			= explode(',', $line);
	//$nowにidと日付がくっついて入ってるので分割
	$exnow =  explode(" ID:", $now);
	//ファイル名とサムネイル名作成
	$exfilename = $ext ? $time.$ext : "";
	$thumbnail = $ext ? $time."s.jpg" : "";

	//親子判定
	$ti = 1;
	$mst = 0;
	foreach ($treelines as $treeline){
		$treelist = explode(",", $treeline);
		if(in_array($no, $treelist)){
			$extree = $treelist[0];
			if($extree == $no){
				$mst = $ti ;
			}
			break;
		}//if treelist extree
		$ti++;
	}//foreach treelines
					
	//各項目を配列に格納
	array_push($arrayDATA, array(
		'no' => $no,			// 記事No
		'date' => $exnow[0],	// 投稿日時
		'name' => $name,		// 投稿者名
		'mail' => $email,		// メールアドレス
		'subject' => $sub,		// 記事タイトル
		'comment' => $com,		// コメント内容
		'url' => $url,			// 投稿者URL
		'host' => $host,		// 投稿者ホスト
		'exid' => $exnow[1],	// 投稿者ID
		'pass' => $pwd,			// パスワード
		'ext' => $ext,			// 投稿画像の拡張子
		'img_w' => $w,			// 投稿画像サイズ
		'img_h' => $h,			// 投稿画像サイズ
		'filename' => $exfilename,	// 画像ファイル名
		'thumbnail' => $thumbnail,	// サムネイル
		'check' => $chk,			// 投稿画像のチェックデジット
		'time' => $ptime,	// お絵かきにかかった時間
		'fontcolor' => $fcolor,	// 投稿者の文字色
		'tree' => $extree, //親ツリー
		'parent' => $mst, //親ツリー順番
	));

} //foreach lines

//配列の順序を逆順にする
$arrayDATA = array_reverse($arrayDATA);

//データベース宣言
try {
    $db = new PDO('sqlite:log/boardlog.db' );

    // テーブル作成
    $db->exec("CREATE TABLE IF NOT EXISTS oekakibbs(id integer primary key,no integer,date text,name text,mail text,subject text,comment text,url text,host text,exid text,pwd text,ext text,img_w text,img_h text,filename text,thumbnail text,cdt text,time text,fontcolor text,tree integer,parent integer,invz integer)");


    // 挿入(プリペアドステートメント)
    $s = $db->prepare("INSERT INTO oekakibbs(no ,date ,name ,mail ,subject ,comment ,url ,host ,exid ,pwd ,ext ,img_w ,img_h ,filename ,thumbnail ,cdt ,time ,fontcolor ,tree ,parent ,invz)
	 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
    
      $s->bindParam(1, $db1);
      $s->bindParam(2, $db2);
      $s->bindParam(3, $db3);
      $s->bindParam(4, $db4);
      $s->bindParam(5, $db5);
      $s->bindParam(6, $db6);
      $s->bindParam(7, $db7);
      $s->bindParam(8, $db8);
      $s->bindParam(9, $db9);
      $s->bindParam(10, $db10);
      $s->bindParam(11, $db11);
      $s->bindParam(12, $db12);
      $s->bindParam(13, $db13);
      $s->bindParam(14, $db14);
      $s->bindParam(15, $db15);
      $s->bindParam(16, $db16);
      $s->bindParam(17, $db17);
      $s->bindParam(18, $db18);
      $s->bindParam(19, $db19);
      $s->bindParam(20, $db20);
      $s->bindParam(21, $db21);

//削除があると記事NOが飛ぶので空白追加ルーチンを入れる
$i =1;
foreach ($arrayDATA as $data){
$db1 = $data['no'];
//idとnoが合わなければ空白行を追加
if($db1 > $i ){ 
	$db2 = $db4 = $db7 = $db9 = $db11 = $db12 = $db13 = $db14 = $db15 = $db16 = $db17 = $db18 = '';
	$db3 = $db5 = $db6 = $db8 = $db10 = 'delete';
	$db19 = $db20 = 0;
	$db21 =1;
	for ($j = $i; $j <= $data['no']; $j++) {
		if($data['no'] == $i ){break;}
		$db1 = $i;
		$s->execute();
		$i++;
	}
}
$invz = 0;


$db1 = $data['no'];
$db2 = $data['date'];
$db3 = $data['name'];
$db4 = $data['mail'];
$db5 = $data['subject'];
$db6 = $data['comment'];
$db7 = $data['url'];
$db8 = $data['host'];
$db9 = $data['exid'];
$db10 = $data['pass'];
$db11 = $data['ext'];
$db12 = $data['img_w'];
$db13 = $data['img_h'];
$db14 = $data['filename'];
$db15 = $data['thumbnail'];
$db16 = $data['check'];
$db17 = $data['time'];
$db18 = $data['fontcolor'];
$db19 = $data['tree'];
$db20 = $data['parent'];
$db21 = $invz;

//dbに追加
      $s->execute()
        or die("<p>書き込みに失敗しました</p>");//失敗処理
	$i ++;
}//追加処理おわり

//追加したデータを表示
//sql呼び出し
$sql = "select * from oekakibbs order by id desc";

//テーブル作成
echo "<table border=\"1\">\n";
echo "<tr><th>DBid</th><th>番号</th><th>日時</th><th>Mail</th><th>題名</th><th>コメント</th><th>URL</th><th>host</th><th>USERID</th><th>Pass</th><th>拡張子</th><th>画像幅</th><th>画像高</th><th>ファイル名</th><th>サムネ</th><th>check</th><th>時間</th><th>カラー</th><th>ツリー</th><th>記事順</th><th>削除</th></tr>\n";

//各コメントデータ表示
foreach ($db->query($sql) as $a) {
      echo "<tr><td>", $a['id'], "</td><td>", $a['no'], "</td><td>",$a['date'], "</td><td>",$a['mail'], "</td><td>",$a['subject'], "</td><td>", mb_strimwidth($a['comment'], 0, 20, "…"), "</td><td>",mb_strimwidth($a['url'], 0, 20, "…"), "</td><td>",mb_strimwidth($a['host'], 0, 20, "…"), "</td><td>",$a['exid'], "</td><td>",$a['pwd'], "</td><td>",$a['ext'], "</td><td>",$a['img_w'], "</td><td>",$a['img_h'], "</td><td>",$a['filename'], "</td><td>",$a['thumbnail'], "</td><td>",$a['cdt'], "</td><td>",$a['time'], "</td><td>",$a['fontcolor'], "</td><td>", htmlspecialchars($a['tree']), "</td><td>",$a['parent'], "</td><td>",$a['invz'], "</td></tr>\n";
    }
//テーブルおわり
echo "</table>\n";

//db切断
   $db = null;
  } catch (PDOException $e) {
    echo "<p>エラー:", $e->getMessage(), "</p>";
  }

echo("作業おわり");
exit;

ログデータに削除記事があった場合、primary idと記事NOが合わなくなるので一応並列記憶
回避ルーチンを入れてるので確認できたらnoの方は消す


なんかプリペアドステートメントが美しくないw
適当すぎるw

POTI-boardのコード編集

某所で設置しているPOTI-board
PHPスクリプト - お絵かき掲示板『POTI-board』 - ぷにゅねっと
お絵描き掲示板としては優秀なんだけど
2008年から更新が止まって色々と問題があるので
問題があるところを抜き出して変更して行くつもり

予定

  • ページ数が増えるとhtml書き出しが重くなるので2ページ目以降は動的に表示させる(済)
  • パスワードのハッシュ方式を最新のものに変える(済)
  • ログをデータベース化する(SQLite)
  • エラー(警告)が多いので改善する
  • セキュリティの向上
  • スマホ表示に対応する(テーブルレイアウトの削除)
  • 初期設定画面をつくる(パスワード等の設定)

変更点

  • ページャー作成
    • updatelog()をコピーしてpaging()をつくる
    • updatelog()の2ページ目以降の書き出し処理は消す
    • 前ページは $dat['prev'] = PHP_SELF.'?p='.$prev/PAGE_DEF;
    • 次ページは $dat['next'] = PHP_SELF.'?p='.$next/PAGE_DEF; に変更
    • 各ページリンクは$pformat = str_replace("", PHP_SELF.'?p='.$i/PAGE_DEF , $pno);
    • ページ数を取得し、echo( htmloutput(MAINFILE, $dat, true)); で表示する
    • 表示ページまで飛ばす処理 if($page != $p && $page < $p) continue ;
    • p=0でindexも表示出来る、表示出来ないページにエラー処理を入れる
  • パスワードハッシュ変更
    • md5をpassword_hash()に変更
    • CreatePass()をつくる
    • $p = password_hash($d, PASSWORD_DEFAULT); return $p;
    • パスワード比較用のVerifyPass()をつくる
    • if (password_verify($d, $p)) return true ;
    • 旧パスワードデータを流用する場合、md5との比較ができるようにする
    • if (OLD_HASH && $n <= OLD_HASH){ md5との比較 }として、指定noより前の書き込みについて比較できるようにする
    • 管理者パスワードはあらかじめハッシュ化したものをconfigに保存しておく
  • SQLite化する(予定)
    • テーブル作成処理を追加
    • 旧ログデータ変換用のスクリプトを別につくる
    • ツリーと表示順も同時に保存
    • テーブル:id番号,日時,Mail,題名,コメント,URL,host,USERID,Pass,拡張子,画像幅,画像高,ファイル名,サムネ,チェックデジット,描画時間,fontカラー,ツリー番号,表示順
    • 親はid番号=ツリー番号、子は表示順=0
    • "select * from bbs order by hyouji asc, tree desc, id asc"でデータ取得(予定)
    • 表示をまとめて関数化する(予定)
  • エラー改善
    • 変数宣言してないところでif(変数)としてる所が多いのでif(isset(変数))にする
    • if(isset(${$a}))${$a}=0; こういう関数つくっても良いかも?
    • テンプレートのifneを使ってる部分が良くエラーに成るのでndefに戻す
  • セキュリティの向上
    • パスワードハッシュ変更(済)
    • 埋め込みパスワードhiddenをセッションに変更
    • extract()をなんとかする
    • eval()もなんとかしたいけどどうすれば良いのかわからんw
    • 今どき管理人用リンクを一般公開してるところも無いのでphp?mode=adminとかでアクセスしてもらう
    • 初回のみ設定用スクリプトを走らせる?wordpressみたいにできたらよいけどめんどそうw
    • 文字コードutf-8固定で良くない? いまさらsjisとかeucとか文字化けのもとだし?
  • スマホ表示に対応する(予定)
    • テーブルレイアウト削除
    • CSSファイルをまとめる
  • 初期設定画面(予定)
    • 最初はconfigファイルを用意せず、config-defaultを入れておく
    • configファイルが無い場合、入力フォーム画面に飛ばす
    • 各項目をフォーム入力→config書き出し保存
    • パスワードは最初に計算、保存

SQLite :Fatal error: Call to a member function bindParam() on a non-object

SQlite使ってて文法は正しいのにエラーが出た件

$db->errorInfo() 

でエラーの内容を見てみると

 Array ( [0] => HY000 [1] => 1 [2] => no such table: テーブル名 ) 

テーブル名が無いと出た

原因

CREATE TABLEで「check」という名前のカラムを作ろうとしたのが原因らしい
適宜済みのキーは避けて別の名前にするか、''などで囲むと良いらしい


SQLiteで定義されているキーワードに関する注意点 - SQLite入門

神戸とか豚まん放浪記2017

さて、前の記事から随分時間が経ったので、備忘録として最近のお店とか覚えてる範囲で記入してみる。

sidewalkcafe.hatenablog.com
↑過去記事


ついでだし、個人的トップ10ランキング。

  1. 新開地 「春陽軒」
  2. 中華街 「老祥記」
  3. 元町商店街 「老祥紀
  4. 湊川 「とんとん餃子」
  5. 板宿 「餃子弁慶」
  6. 三宮 「太平閣」
  7. 王子公園 「ぶたまんや」
  8. 元町 「四興楼」
  9. 平野 「中央ベーカリー」
  10. 三宮・なんば他「551蓬莱
  11. 垂水 蒸龍
  12. 元町・三宮 「三宮一貫楼」

神戸市内

不明・閉店