'score', 'newcome']; public function __construct() { $this->dbh = new PDO('sqlite:db/score.db'); $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->set_sort_mode(filter_input(INPUT_GET, 'sort')); } public function get_defines() { $id_define_array = []; $tables = [['race', 'races'], ['class', 'classes'], ['personality', 'personalities'], ['realm', 'realms']]; foreach ($tables as $t) { $id_define_array[$t[0]] = []; $stmt = $this->dbh->query("SELECT {$t[0]}_id, {$t[0]}_name FROM {$t[1]} ORDER BY {$t[0]}_id"); foreach ($stmt->fetchAll(PDO::FETCH_NUM) as $row) { $id_define_array[$t[0]][intval($row[0])] = $row[1]; } } return $id_define_array; } /** * スコア表示モードを設定する * * @param string $mode 設定する表示モード * 'score' - スコア順に表示(デフォルト) * 'newcome' - 新着順に表示 * 存在しない表示モードが指定された場合デフォルトの表示モードが設定される */ public function set_sort_mode($mode) { if ($mode !== NULL && in_array($mode, self::$sort_mode_list)) { $this->sort_mode = $mode; } else { $this->sort_mode = self::$sort_mode_list['default']; } } /** * スコア表示モード名を取得する * * @return string スコア表示モード名 */ public function get_sort_mode_name() { switch ($this->sort_mode) { case 'score': return "スコア順"; case 'newcome': return "新着順"; default: return "不明"; } } /** * スコア検索の絞り込み用WHERE句を取得する * * @return array スコア検索絞り込み用のWHERE句('where')とプレースホルダに渡すパラメータ配列('params')の連想配列 */ private function get_search_condition() { $last_days = filter_input(INPUT_GET, 'last_days', FILTER_VALIDATE_INT); $params = []; if ($last_days > 0) { $wheres[] = "date >= datetime('now', 'localtime', '-{$last_days} days')"; } foreach (['race_id', 'class_id', 'personality_id'] as $key_column) { $val = filter_input(INPUT_GET, $key_column, FILTER_VALIDATE_INT); if ($val > 0) { $wheres[] = "{$key_column} = :{$key_column}"; $params[":{$key_column}"] = $val; } } foreach (['realm_id1', 'realm_id2'] as $idx => $key_column) { $val = filter_input(INPUT_GET, $key_column, FILTER_VALIDATE_INT); if ($val > 0) { $seq = $idx + 1; $wheres[] = "score_id IN (SELECT score_id FROM score_realms WHERE realm_id = :{$key_column} AND realm_seq = {$seq})"; $params[":{$key_column}"] = $val; } } foreach (['name'] as $key_column) { $val = filter_input(INPUT_GET, $key_column); $match_mode = filter_input(INPUT_GET, $key_column."_match"); if ($val !== NULL && strlen($val) > 0) { $wheres[] = "{$key_column} LIKE :{$key_column}"; $params[":{$key_column}"] = ($match_mode == "strict") ? $val : "%".$val."%"; } } foreach (['killer'] as $key_column) { $val = filter_input(INPUT_GET, $key_column); if ($val !== NULL && strlen($val) > 0) { $wheres[] = "({$key_column} LIKE :{$key_column} OR {$key_column} = :{$key_column}1 OR {$key_column} = :{$key_column}2)"; $params[":{$key_column}"] = "%".$val."%"; $params[":{$key_column}1"] = "麻痺状態で".$val; $params[":{$key_column}2"] = "彫像状態で".$val; } } $result['where'] = isset($wheres) ? 'WHERE '.implode(' AND ', $wheres) : ''; $result['params'] = $params; return $result; } /** * スコアソート用のORDER BY句を取得する * * @return string スコアソート用のORDER BY句 */ private function get_order_by() { switch ($this->sort_mode) { case "score": $order_by = "ORDER BY score DESC"; break; case "newcome": $order_by = 'ORDER BY score_id DESC'; break; } return $order_by; } /** * スコア検索用のSQLクエリを取得する * * @param integer $offset スコア取得開始位置 * @param integer $limit スコア取得最大件数 * @param string $where スコア検索に用いるWHERE句 * @param string $order_by スコアソートに用いるORDER BY句 * @return string スコア検索用SQLクエリ */ private function get_search_query($offset, $limit, $where, $order_by) { $query = <<dbh->prepare("SELECT count(*) AS data_count from scores {$where}"); $stmt->execute($params); $res = $stmt->fetchAll(PDO::FETCH_ASSOC); return intval($res[0]['data_count']); } /** * スコアを検索する * * @param integer $start_num 検索するスコアの開始位置 * @param integer $count 検索するスコア数 * * @return array 検索したスコアの配列と、条件に合致するスコアの総件数の連想配列 */ public function search_score($start_num, $count) { $cond = $this->get_search_condition(); $order_by = $this->get_order_by(); $query_sql = $this->get_search_query(intval($start_num / $count) * $count, $count, $cond['where'], $order_by); $search_start_time = microtime(true); $this->dbh->beginTransaction(); $score_stmt = $this->dbh->prepare($query_sql); $score_stmt->execute($cond['params']); $result['scores'] = $score_stmt->fetchAll(PDO::FETCH_ASSOC); $result['total_data_count'] = $this->get_query_data_count($cond['where'], $cond['params']); $this->dbh->commit(); $result['elapsed_time'] = microtime(true) - $search_start_time; return $result; } public function get_db_handle() { return $this->dbh; } private function update_killers_cache_table() { $this->dbh->exec("DROP TABLE IF EXISTS killers_cache"); $this->dbh->exec(<<dbh->beginTransaction(); if (!$this->get_cache_status('killers_cache')) { $this->update_killers_cache_table(); $this->update_cache_status('killers_cache', 1); } $this->dbh->commit(); } catch (PDOException $e) { $this->dbh->rollBack(); } $killers = $this->dbh->query("SELECT * FROM killers_cache ORDER BY killer_count_total DESC")->fetchAll(PDO::FETCH_ASSOC); return $killers; } /** * 統計情報のキャッシュテーブルを更新する * * 種族・職業・性格について各種統計情報を取得しキャッシュテーブルに保存する * 通常の統計情報の取得はこのキャッシュテーブルから行う */ private function update_statistics_cache_tables() { $statistics_list = ['race', 'class', 'personality']; foreach ($statistics_list as $stat) { $table_name = $stat."_statistics"; $this->dbh->exec("DROP TABLE IF EXISTS ".$table_name); $this->dbh->exec( <<dbh->exec("DROP TABLE IF EXISTS ".$table_name); $this->dbh->exec( <<dbh->beginTransaction(); if (!$this->get_cache_status('statistics_cache')) { $this->update_statistics_cache_tables(); $this->update_realm_statistics_cache_tables(); $this->update_cache_status('statistics_cache', 1); } $this->dbh->commit(); } catch (PDOException $e) { $this->dbh->rollBack(); } $stat = []; $this->dbh->beginTransaction(); foreach ([['race', 'races'], ['class', 'classes'], ['personality', 'personalities']] as $kind) { $stmt = $this->dbh->query("SELECT ${kind[0]}_id AS id, ${kind[0]}_name AS name, * FROM ${kind[0]}_statistics NATURAL JOIN ${kind[1]} ORDER BY ${sort_key_column} DESC"); $stat[$kind[0]] = $stmt->fetchAll(PDO::FETCH_ASSOC); } foreach (['realm1', 'realm2'] as $realm) { $stmt = $this->dbh->query("SELECT * FROM {$realm}_statistics;"); $stat[$realm] = $stmt->fetchAll(PDO::FETCH_ASSOC); } $this->dbh->commit(); return $stat; } public function table_exists($table_name) { $stmt = $this->dbh->prepare("SELECT count(*) AS table_exists FROM sqlite_master WHERE type='table' AND name=?"); $stmt->execute([$table_name]); $res = $stmt->fetchAll(PDO::FETCH_ASSOC); return intval($res[0]['table_exists']) === 1; } public function get_cache_status($cache_table_name) { if (!$this->table_exists('cache_status_table')) { $this->create_cache_status_table(); } $stmt = $this->dbh->prepare("SELECT cached FROM cache_status_table WHERE table_name=?"); $stmt->execute([$cache_table_name]); $res = $stmt->fetchAll(PDO::FETCH_ASSOC); return count($res) === 1 ? intval($res[0]['cached']) : 0; } public function update_cache_status($cache_table_name, $status) { if (!$this->table_exists('cache_status_table')) { $this->create_cache_status_table(); } $stmt = $this->dbh->prepare("INSERT OR REPLACE INTO cache_status_table VALUES(?, ?)"); $stmt->execute([$cache_table_name, $status]); return $stmt->rowCount(); } public function create_cache_status_table() { $this->dbh->exec(<<dbh->prepare( <<dbh->prepare( <<dbh->beginTransaction(); if ($insert_stmt->execute($score_data['character_info']) === FALSE || $insert_stmt->rowCount() !== 1) { $dbh->rollBack(); return FALSE; } // NOTE: score_idはINTEGER PRIMARY KEYなのでROWIDを参照している // したがってlastInsertIdで追加されたスコアのscore_idを取得可能 $score_id = $this->dbh->lastInsertId(); foreach ($score_data['realm_info'] as $realm_seq => $realm_name) { if ($realm_insert_stmt->execute([$score_id, $realm_seq + 1, $realm_name]) === FALSE || $realm_insert_stmt->rowCount() !== 1) { $dbh->rollBack(); return FALSE; } } // スコアが追加されたので死因ランキング・人気ランキングのキャッシュをクリア $this->update_cache_status('killers_cache', 0); $this->update_cache_status('statistics_cache', 0); $this->dbh->commit(); return $score_id; } catch (PDOException $e) { $dbh->rollBack(); } return FALSE; } }