データの保管

学校裏サイトの掲示板データやユーザーデータ等を保存する術を紹介します。第一回や第二回では、単純テキストファイルに保存していました。ファイルへの操作が「追記」と「中身をそのまま出力」だけと単純であるため十分でした。

しかし、追記以外に挿入や置換・削除、一部だけ取得や並び替えての表示をする場合は不十分です。つまり、スレッドや編集・削除機能を作るには不足しているのです。


配列

配列を使うことで、柔軟な操作が可能になります。これからは紹介するデータ形式はどれも、基本的に配列のアプローチです。


CSV/TSV

CSVやTSVは、ある文字で値を区切る方式です。ファイルを直接、見やすく編集しやすいです。処理も速いです。基本的に二次元構造です。

書き込むときの注意は、一行の中に改行が入らないようにすることと値に「区切り文字」が含まれないようにすることです。

区切り文字は、CSVでは「,」でTSVでは「\t」(ダブルクオーテーションで囲うことでTABの意味)です。

<?php
/*
配列と文字列の変換について
implode('区切り文字', [配列]) で配列を文字列(ファイルに書く内容)に変換
explode('区切り文字', '文字列') でファイル等から取得しな文字列(ただし一行だけ)を配列に変換

他の言語の場合は、splitなどを使用
*/


function row(string $s){
    return preg_replace('/\r\n|\r|\n/', '', $s);
}

$split = ','; //csvの場合
$split = "\t"; //tsvの場合

/*
区切り文字を取り去るか別の文字に変換します
str_replace($split, '', $_POST['投稿内容']) の処理を最初にしておきます。
*/




//書き込み(ファイルの終端は常に改行にするべし) ファイルが既存でも可
file_put_contents('ファイルのパス(場所)', row(implode($split, ['値の配列'])).PHP_EOL, FILE_APPEND | LOCK_EX);

//読み取り
#方法1
$array = [];
foreach (file('ファイルのパス', FILE_IGNORE_NEW_LINES) as $row){
    $array[] = explode($split, $row);
}

#方法2
$array = [];
$f = fopen('ファイルのパス', 'r');
while (($i = fgets($f)) !== false){
    if (empty($i)){
        continue;
    }
    $array[] = explode($split, $i);
}
fclose($f);

#特定の行だけを取得する便利関数
function get_row(string $file, int $l){
    if (!file_exists($file)){
        return false;
    }
    $fp = fopen($file, 'r');
    for ($i=0;$i < $l;++$i){
        $out = fgets($fp);
    }
    fclose($fp);
    if (isset($out)){
        return trim($out);
    } else {
        return false;
    }
}



/* 柔軟な取り扱い */
//ファイルの先頭に追記
$old = file_get_contents('ファイルのパス');
$new = '追加する文字列'.PHP_EOL.$old;
file_put_contents('ファイルのパス', $new, LOCK_EX);

//置換(メッセージ編集機能の元)
$content = file_get_contents('ファイルのパス'); //ファイル全体の内容
$edit_row = get('ファイルのパス', 当該行番号(整数)).PHP_EOL; //置換対象の行
$replace = '新しい文字列'.PHP_EOL; //置換後の行
file_put_contents('ファイルのパス', str_replace($edit_row, $replace, $content), LOCK_EX);


//連番ID機能(順番が入れ替わるので先頭追記をしないこと。一列目を連番IDとする)
//編集機能で「衝突(全く同じ内容の行が複数あると余計な部分も置換されてしまう)」を防止する効果あり
$id = substr_count(file_get_contents('ファイルのパス', PHP_EOL)); //行数を取得
$id += 1; //新しいID
file_put_contents('ファイルのパス', implode('区切り文字', [$id, 値1, 値2, 値n]).PHP_EOL, FILE_APPEND | LOCK_EX); //追記


//配列の並び替えや検索等
//$arrayは配列の配列(「読み取り」で使った$arrayが該当します)
$result = [];
foreach ($array as $v){
    if (strpos($v[対象の列番号(整数)], '検索ワード') !== false){
        //並び替えの基準
        $s = $v[対象の列番号]; //新しい順であればこれは不要。各列で全て異なる値のものを使用する
        $result[$s] = $v; //配列の配列が作成
    }
}
ksort($result); //対象の列番号を基準に並び替えた

このように、ファイル操作と文字列操作で基本的な機能が実現出来ますが、一つだけのファイルでは二次元的な処理しか出来ません。(三次元以上の複雑な処理は考えるのも面倒だし裏サイト程度じゃ使う機会ない)

人によっては操作がちょっと面倒かもしれません。しかし、ファイルの利点は管理をOSに丸投げできることにあります。ディレクトリ等を駆使して複数ファイルを扱うことも可能です。

<?php
//ファイルを新しい順にする
$file_list = [];
foreach (scandir('ディレクトリのパス') as $file){
    if (($file === '.') || ($file === '..')){
        continue;
    }
    $file_list[filemtime('ディレクトリのパス'.'/'.$file)] = 'ディレクトリのパス'.'/'.$file;
}
krsort($file_list); //ファイルの最終更新日(unixタイム)が大きいほど新しいので降順に並び替え


//新しいスレッドの作成(CSVやTSVは二次元構造なので一つのファイルに複数スレッドを入れると操作が困難です)
$thread_dir = 'スレッドのファイルを格納しているディレクトリ';
$thread_id = 0;
#スレッドidを連番にする(1スタートの整数)
foreach (scandir($thread_dir) as $thread){
    if (preg_match('/^([0-9]+\.(tsv|csv))$/', $thread) === 1){
        if ($thread_id < (int)preg_replace('/\.(tsv|csv)/', '', $thread)){
            $thread_id = (int)preg_replace('/\.(tsv|csv)/', '', $thread);
        }
    }
}
$thread_id += 1;
$thread_file = $thread_dir.'/'.$thread_id.'.csv'; //csvかtsvの拡張子をつける
file_put_contents($thread_file, '新規スレッドに書き込む文字列'.PHP_EOL, LOCK_EX);

このように、スレッド機能も可能ですが面倒です。


json

CSVやTSVは、内容を配列に変換して柔軟に扱えますが基本的に「連想配列」にはなりません。つまり列を番号で指定出来るが名前で指定できないのです。

しかし、jsonは連想配列にすることが出来ます。つまりCSVやTSVよりもさらに柔軟な扱いが可能で、しかも二次元より大きい次元の構造が可能です。

ただし、それぞれの値に名前を付ける分、データ量が多く、処理の重くなります。

PHPでは、json_decodeとjson_encodeで、ファイル内容と連想配列の変換が出来ます。むしろ、文字列操作で連想配列を作る方が難しいです。

筆者はjsonはあまり使わないので多くは語れません。因みにjsonを使って掲示板を作ったというサイトがありますので紹介します。プシューIPS


SQL

最も一般的な方法です。最も柔軟にデータを扱え、また大量なデータにも使えます。通常のファイルを使う方法では、何Tバイトも扱えません(たぶん破損するかメモリー破壊する)が、SQLのなかにはそれが出来るものもあります。

また、SQLにはテーブル(エクセルで言う「シート」のようないもの)の概念があります。わざわざファイルを何個も扱う必要がありません。

裏サイト程度ではそんな膨大なデータを扱うことはないでしょう。

しかし、SQLにはファイル形式とは大きく違い特徴があり、中身がバイナリーです。テキストファイルで編集できません。常にSQLクエリで操作します。

SQLにはいくつも種類がありますが、大抵のレンタルサーバーでは「MySQL」「MariaDB」「SQLite」のうちどれかが使えます。

特に「SQLite」は「SQL使えません」と書いてあるサーバーでもこれだけは使える場合があり、字の通り「軽い」です。

筆者はうまく使いこなせず、CSVやTSVで十分だと思いました。

<?php
//sqliteを使う
$db_file = 'データベースファイルのパス'; //webでアクセス出来ない場所に保管することを推奨
$db = PDO('sqlite:'.$db_file);
$db->exec('SQL文(内容を書き換える趣旨)');
$result = $db->query('SQL文(内容を取得する趣旨)');

//SQLで取得した内容は連想配列で得られます。

SQLインジェクションには十分注意して下さい。WAFの導入も検討しましょう。


独自フォーマット

自分でルールを決めます。CSV系ファイルをカスタムしたようなものになっています。

例えば、一行目を名前にしてCSVでも疑似的に連想配列で取得出来るようにしたり、区切り文字や囲い文字を変更したりします。

ファイル名にも情報を記載する。(時刻を付けたりする)

wiki構文のような置換文字を使う。

などなど。


おすすめは

学校裏サイト程度ではTSVで十分でしょう。このサイトも基本はTSVを使用しています。

もし、配列の扱いがよく分からないならば、通常のテキストファイルで結構です。無理に編集機能などを付けてバグを産むより良いです。

筆者は、よくプログラミングを分からないまま無理やり編集機能や並び替えをやった結果、非常に馬鹿げた糞コードを生成しました。労力の割にはエラーの多いガラクタです。


余談

PHPは簡単とか言われてますが、全然そんなことないです。

マジで難しかったです。最初はコピペしかできず絶望的でした。まず読めない。「echo」の意味すら知らなかったです。「このページは動作していません」を何度見たことやら。

それ以前に、HTMLでも詰まります。「h1タグ」は文字を大きくするタグだと本気で勘違いしていたこともありました。さらに「formタグ」を設置すれば「投稿機能」が作れると勘違いしたことも。

つまり物凄い苦労したということです。独学だったからといっても、これは「簡単」とは言えませんね。

では、なぜ簡単だと言われるのかというと、既に他の言語を習得している人にとっては簡単だということです。あるいは、他の言語が超難しいのでしょう。

急ぎで裏サイトを作りたい方は、生成AIでも使って下さい。数秒で完成します。



次回: プログラミング嫌い


前回: 荒らし対策




広告