4 private static $sort_mode_list = ['default' => 'score', 'newcome'];
6 public function __construct() {
7 $this->dbh = new PDO('sqlite:db/score.db');
8 $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
10 $this->set_sort_mode(filter_input(INPUT_GET, 'sort'));
16 * @param string $mode 設定する表示モード
17 * 'score' - スコア順に表示(デフォルト)
19 * 存在しない表示モードが指定された場合デフォルトの表示モードが設定される
21 public function set_sort_mode($mode)
23 if ($mode !== NULL && in_array($mode, self::$sort_mode_list)) {
24 $this->sort_mode = $mode;
26 $this->sort_mode = self::$sort_mode_list['default'];
34 * @return string スコア表示モード名
36 public function get_sort_mode_name()
38 switch ($this->sort_mode) {
50 * スコア検索の絞り込み用WHERE句を取得する
52 * @return array スコア検索絞り込み用のWHERE句('where')とプレースホルダに渡すパラメータ配列('params')の連想配列
54 private function get_search_condition()
56 $last_days = filter_input(INPUT_GET, 'last_days', FILTER_VALIDATE_INT);
60 $wheres[] = "date >= datetime('now', 'localtime', '-{$last_days} days')";
63 foreach (['race_id', 'class_id', 'personality_id'] as $key_column) {
64 $val = filter_input(INPUT_GET, $key_column, FILTER_VALIDATE_INT);
66 $wheres[] = "{$key_column} = :{$key_column}";
67 $params[":{$key_column}"] = $val;
71 foreach (['name'] as $key_column) {
72 $val = filter_input(INPUT_GET, $key_column);
73 if ($val !== NULL && strlen($val) > 0) {
74 $wheres[] = "{$key_column} = :{$key_column}";
75 $params[":{$key_column}"] = $val;
79 foreach (['killer'] as $key_column) {
80 $val = filter_input(INPUT_GET, $key_column);
81 if ($val !== NULL && strlen($val) > 0) {
82 $wheres[] = "({$key_column} LIKE :{$key_column} OR {$key_column} = :{$key_column}1 OR {$key_column} = :{$key_column}2)";
83 $params[":{$key_column}"] = "%".$val."%";
84 $params[":{$key_column}1"] = "麻痺状態で".$val;
85 $params[":{$key_column}2"] = "彫像状態で".$val;
89 $result['where'] = isset($wheres) ? 'WHERE '.implode(' AND ', $wheres) : '';
90 $result['params'] = $params;
97 * スコアソート用のORDER BY句を取得する
99 * @return string スコアソート用のORDER BY句
101 private function get_order_by()
103 switch ($this->sort_mode) {
105 $order_by = "ORDER BY score DESC";
108 $order_by = 'ORDER BY score_id DESC';
119 * @param integer $offset スコア取得開始位置
120 * @param integer $limit スコア取得最大件数
121 * @param string $where スコア検索に用いるWHERE句
122 * @param string $order_by スコアソートに用いるORDER BY句
123 * @return string スコア検索用SQLクエリ
125 private function get_search_query($offset, $limit, $where, $order_by)
130 group_concat(realm_name) AS realms_name,
132 WHEN killer = 'ripe' THEN '勝利の後引退'
133 WHEN killer = 'Seppuku' THEN '勝利の後切腹'
134 ELSE killer || 'に殺された'
143 LIMIT {$offset}, {$limit}) AS s
163 * 検索でヒットしたスコアの総件数を取得する
165 * @param string $where スコア検索に用いるWHERE句
166 * @return integer スコア総件数
168 private function get_query_data_count($where, $params)
170 $stmt = $this->dbh->prepare("SELECT count(*) AS data_count from scores {$where}");
171 $stmt->execute($params);
172 $res = $stmt->fetchAll(PDO::FETCH_ASSOC);
174 return intval($res[0]['data_count']);
181 * @param integer $start_num 検索するスコアの開始位置
182 * @param integer $count 検索するスコア数
184 * @return array 検索したスコアの配列と、条件に合致するスコアの総件数の連想配列
186 public function search_score($start_num, $count)
188 $cond = $this->get_search_condition();
189 $order_by = $this->get_order_by();
190 $query_sql = $this->get_search_query(intval($start_num / $count) * $count, $count, $cond['where'], $order_by);
192 $search_start_time = microtime(true);
193 $this->dbh->beginTransaction();
195 $score_stmt = $this->dbh->prepare($query_sql);
196 $score_stmt->execute($cond['params']);
197 $result['scores'] = $score_stmt->fetchAll(PDO::FETCH_ASSOC);
198 $result['total_data_count'] = $this->get_query_data_count($cond['where'], $cond['params']);
200 $this->dbh->commit();
201 $result['elapsed_time'] = microtime(true) - $search_start_time;
206 public function get_db_handle() {
210 private function update_killers_cache_table()
213 $this->dbh->beginTransaction();
215 $this->dbh->exec("DROP TABLE IF EXISTS killers_cache");
216 $this->dbh->exec(<<<EOM
222 count(*) AS killer_count_total,
223 count(killed_status = 0 OR NULL) AS killer_count_normal,
224 count(killed_status != 0 OR NULL) AS killer_count_freeze
228 WHEN killer LIKE '麻痺状態で%' THEN substr(killer, 6)
229 WHEN killer LIKE '彫像状態で%' THEN substr(killer, 6)
230 WHEN killer = 'ripe' THEN '引退'
231 WHEN killer = 'Seppuku' THEN '切腹'
235 WHEN killer LIKE '麻痺状態で%' THEN 1
236 WHEN killer LIKE '彫像状態で%' THEN 2
245 killer_count_total DESC
248 $this->dbh->commit();
251 } catch (PDOException $e) {
252 $this->dbh->rollBack();
258 public function get_killers_table()
260 //$this->update_cache_status('killers_cache', 0);
261 if (!$this->get_cache_status('killers_cache')) {
262 $this->update_killers_cache_table();
263 $this->update_cache_status('killers_cache', 1);
266 $killers = $this->dbh->query("SELECT * FROM killers_cache ORDER BY killer_count_total DESC")->fetchAll(PDO::FETCH_ASSOC);
273 * 統計情報のキャッシュテーブルを更新する
275 * 種族・職業・性格について各種統計情報を取得しキャッシュテーブルに保存する
276 * 通常の統計情報の取得はこのキャッシュテーブルから行う
278 * @return boolean 生成に成功したらTRUE、失敗したらFALSE
280 private function update_statistics_cache_tables() {
281 $statistics_list = ['race', 'class', 'personality'];
284 foreach ($statistics_list as $stat) {
285 $table_name = $stat."_statistics";
286 $this->dbh->exec("DROP TABLE IF EXISTS ".$table_name);
287 $stmt = $this->dbh->query(
289 CREATE TABLE $table_name AS
292 count(sex=1 or NULL) AS male_count,
293 count(sex=0 or NULL) AS female_count,
294 count(*) AS total_count,
295 count(winner=1 OR NULL) AS winner_count,
296 avg(score) AS average_score,
297 max(score) AS max_score
305 } catch (PDOException $e) {
314 * @param integer $sort_key_column 表示順序のキーとするカラムの名称
318 public function get_statistics_tables($sort_key_column) {
319 if (!$this->get_cache_status('statistics_cache')) {
320 $result = $this->update_statistics_cache_tables();
321 $this->update_cache_status('statistics_cache', $result ? 1 : 0);
326 $this->dbh->beginTransaction();
327 foreach ([['race', 'races'], ['class', 'classes'], ['personality', 'personalities']] as $kind) {
328 $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");
329 $stat[$kind[0]] = $stmt->fetchAll(PDO::FETCH_ASSOC);
331 $this->dbh->commit();
336 public function table_exists($table_name)
338 $stmt = $this->dbh->prepare("SELECT count(*) AS table_exists FROM sqlite_master WHERE type='table' AND name=?");
339 $stmt->execute([$table_name]);
340 $res = $stmt->fetchAll(PDO::FETCH_ASSOC);
342 return intval($res[0]['table_exists']) === 1;
346 public function get_cache_status($cache_table_name)
348 if (!$this->table_exists('cache_status_table')) {
349 $this->create_cache_status_table();
352 $stmt = $this->dbh->prepare("SELECT cached FROM cache_status_table WHERE table_name=?");
353 $stmt->execute([$cache_table_name]);
354 $res = $stmt->fetchAll(PDO::FETCH_ASSOC);
356 return count($res) === 1 ? intval($res[0]['cached']) : 0;
359 public function update_cache_status($cache_table_name, $status)
361 if (!$this->table_exists('cache_status_table')) {
362 $this->create_cache_status_table();
365 $stmt = $this->dbh->prepare("INSERT OR REPLACE INTO cache_status_table VALUES(?, ?)");
366 $stmt->execute([$cache_table_name, $status]);
368 return $stmt->rowCount();
371 public function create_cache_status_table()
373 $this->dbh->exec(<<<EOM
374 CREATE TABLE cache_status_table
375 (table_name TEXT PRIMARY KEY,
381 public function register_new_score($score_data)
383 $insert_stmt = $this->dbh->prepare(
386 (version, score, name,
387 race_id, class_id, personality_id,
388 sex, level, depth, maxlv, maxdp,
389 au, turns, winner, killer)
391 :version, :score, :name,
392 race_id, class_id, personality_id,
393 :sex, :level, :depth, :maxlv, :maxdp,
394 :au, :turns, :winner, :killer
396 races, classes, personalities
398 race_name = :race AND
399 class_name = :class AND
400 personality_name = :personality
404 $realm_insert_stmt = $this->dbh->prepare(
406 INSERT INTO score_realms
418 $this->dbh->beginTransaction();
419 if ($insert_stmt->execute($score_data['character_info']) === FALSE ||
420 $insert_stmt->rowCount() !== 1) {
425 // NOTE: score_idはINTEGER PRIMARY KEYなのでROWIDを参照している
426 // したがってlastInsertIdで追加されたスコアのscore_idを取得可能
427 $score_id = $this->dbh->lastInsertId();
429 foreach ($score_data['realm_info'] as $realm_name) {
430 if ($realm_insert_stmt->execute([$score_id, $realm_name]) === FALSE ||
431 $realm_insert_stmt->rowCount() !== 1) {
437 // スコアが追加されたので死因ランキング・人気ランキングのキャッシュをクリア
438 $this->update_cache_status('killers_cache', 0);
439 $this->update_cache_status('statistics_cache', 0);
440 $this->dbh->commit();
443 } catch (PDOException $e) {