サンプル・プログラムの実行例
サンプル・プログラム
averageAge.php | サンプル・プログラム本体。 |
pahooInputData.php | データ入力に関わる関数群。 使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。 |
バージョン | 更新日 | 内容 |
---|---|---|
4.8.1 | 2024/04/07 | storeBirthdays(), makeCommonBody() -- bug-fix |
4.8.0 | 2024/02/04 | 名前の区切り文字に空白を追加,pahooInputData対応 |
4.7.0 | 2023/07/30 | 名前の末尾の「さん」を読み飛ばす |
4.6.0 | 2023/01/22 | 英全角を含む氏名を対象に加えた. |
4.5.0 | 2022/12/18 | PHP8.2対応 |
バージョン | 更新日 | 内容 |
---|---|---|
1.7.0 | 2024/10/09 | validURL() validEmail() 追加 |
1.6.0 | 2024/10/07 | isButton() -- buttonタグに対応 |
1.5.0 | 2024/01/28 | exitIfExceedVersion() 追加 |
1.4.2 | 2024/01/28 | exitIfLessVersion() メッセージ修正 |
1.4.1 | 2023/09/30 | コメントの訂正 |
テーブル定義
TABLE_BIRTHDAY:出演者情報 | |||
---|---|---|---|
No. | 名前 | 型 | 内容 |
1 | id | 整数 | 出演者ID |
2 | name | テキスト | 氏名 |
3 | birthday | テキスト | 生年月日 |
4 | dt | テキスト | 登録日時 |
解説:初期化
averageAge.php
179: /**
180: * データベースを初期化する.
181: * データベースが存在しなければCREATEする.
182: * @param なし
183: * @return bool TRUE/FALSE
184: */
185: function initialize() {
186: try {
187: $pdo = new PDO('sqlite:' . DBFILE);
188: $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
189:
190: //テーブル作成:出演者情報
191: //id:出演者ID, name:氏名,birthday:生年月日,dt:登録更新日時
192: $pdo->exec('CREATE TABLE IF NOT EXISTS ' . TABLE_BIRTHDAY . '(
193: id INTEGER PRIMARY KEY AUTOINCREMENT,
194: name TEXT,
195: birthday TEXT,
196: dt TEXT
197: )');
198: $res = TRUE;
199: } catch (PDOException $e) {
200: $res = FALSE;
201: }
202:
203: return $res;
204: }
登録する人物(レコード)は同姓同名の方がいるかもしれないので、それらをも識別できるように、ユニークな番号 ID を追加した。ID は整数だが、AUTOINCREMENT を付与することで INSERT文 を実行するときに自動的に加算していくので、重複する ID を付与することはない。
解説:DBから生年月日を取得
averageAge.php
234: /**
235: * 指定した出演者一覧文字列から出演者の情報を取り出し配列に格納する.
236: * @param string $userlist 出演者一覧(区切り文字:改行および空白)
237: * @param array $users 情報を格納する配列
238: * @return string エラーメッセージ/''
239: */
240: function getBirthdays($userlist, &$users) {
241: if ($userlist == '') return 'ユーザー情報がありません';
242:
243: //出演者一覧を分解
244: $userlist = preg_replace('/[\((][^\))]+[\))]/ui', '', $userlist);
245: //括弧を読み飛ばす
246: $arr = preg_split("/[\n\t&\s]+/ui", $userlist); //氏名を配列に代入
247: $cnt = 0;
248: foreach ($arr as $val) {
249: $user = trim($val);
250: if ($user == '') continue;
251: //英全角、ひらがな、カタカナ、漢字以外で始まるテキストを読み飛ばす
252: if (preg_match('/^[A-Za-zぁ-んァ-ヶ一-龠々]/ui', $user) == 0) {
253: continue;
254: }
255: $user = preg_replace('/さん$/ui', '', $user); //「さん」を読み飛ばす。
256: $users[$cnt]['name'] = $user;
257: $users[$cnt]['birthday'] = '';
258: $users[$cnt]['id'] = '';
259: $cnt++;
260: }
261:
262: //DB検索
263: try {
264: $pdo = new PDO('sqlite:' . DBFILE);
265: $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
266: $stmt = $pdo->prepare(PRE_SELECT);
267: $ret = $stmt->execute();
268: while ($row = $stmt->fetch()) {
269: foreach ($users as $key=>$user) {
270: if ($user['name'] == $row['name']) {
271: $users[$key]['birthday'] = $row['birthday'];
272: $users[$key]['id'] = $row['id'];
273: }
274: }
275: }
276: $pdo = NULL;
277: } catch (PDOException $e) {
278: return 'DBアクセスに失敗しました';
279: }
280:
281: return '' ;
282: }
次に、データベースを検索し、$users['name'] と合致する氏名があれば、その生年月日とIDを配列 $users に代入する。IDを取得するのは、編集を行う際、レコード抽出のキーとするためだ。
解説:氏名、生年月日をDB登録/更新
averageAge.php
380: /**
381: * 氏名、生年月日をデータベースに登録または更新する.
382: * ID番号があればUPDATE文を実行する.
383: * @param array $ids ユーザーID
384: * @param array $names 氏名
385: * @param array $birthday 生年月日
386: * @return string エラーメッセージ/''
387: */
388: function storeBirthdays($ids, $names, $birthdays) {
389: $msg = '';
390: $res = TRUE;
391: $errmsg = '';
392: $patterns = array("/\p{Cc}/u"); //制御文字を除く
393: $cnt = count($names);
394:
395: $pdo = new PDO('sqlite:' . DBFILE);
396: $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
397:
398: //個別処理
399: for ($i = 0; $i < $cnt; $i++) {
400: //バリデーション
401: if (validString($names[$i], $errmsg, NAME_MIN, NAME_MAX, FALSE, $patterns) == FALSE) {
402: return '名前が' . $errmsg;
403: }
404: //年月日区切り文字対策
405: $pat = '/([0-9]+)[年\/]([0-9]*)[月\/]?([0-9]*)[日\/]?/iu';
406: if (preg_match($pat, $birthdays[$i], $arr) > 0) {
407: $yy = sprintf('%04d', $arr[1]);
408: $mm = isset($arr[2]) ? sprintf('%02d', $arr[2]) : '??';
409: $dd = isset($arr[2]) ? sprintf('%02d', $arr[3]) : '??';
410: $birth = $yy . $mm . $dd;
411: } else {
412: $birth = $birthdays[$i];
413: }
414: if (validString($birth, $errmsg, 4, 8, FALSE, array('/^[^0-9]+$/i'), TRUE) == FALSE) {
415: return '生年月日が' . $errmsg;
416: }
417:
418: //重複チェック
419: $stmt = $pdo->prepare(PRE_COUNT);
420: $stmt->bindValue(':name', $names[$i], PDO::PARAM_STR);
421: $stmt->execute();
422: $res = $stmt->fetch();
423:
424: //UPDATE実行
425: if ($res[0] > 0) {
426: $stmt = $pdo->prepare(PRE_UPDATE);
427: $stmt->bindValue(':id', $ids[$i], PDO::PARAM_INT);
428: $stmt->bindValue(':name', $names[$i], PDO::PARAM_STR);
429: $stmt->bindValue(':birthday', $birth, PDO::PARAM_STR);
430: $stmt->bindValue(':dt', date(DATE_W3C, time()), PDO::PARAM_STR);
431:
432: //INSERT実行
433: } else {
434: $stmt = $pdo->prepare(PRE_INSERT);
435: $stmt->bindValue(':name', $names[$i], PDO::PARAM_STR);
436: $stmt->bindValue(':birthday', $birth, PDO::PARAM_STR);
437: $stmt->bindValue(':dt', date(DATE_W3C, time()), PDO::PARAM_STR);
438: }
439: $res = $stmt->execute();
440: if ($res == FALSE) return 'DB登録に失敗';
441: }
442:
443: return '';
444: }
引数として、ユーザーIDの配列 $ids、氏名の配列 $names、生年月日の配列 $birthdays を与える。一度の呼び出しで、配列にある複数の出演者の登録、更新ができる。
まず、入力値のバリデーションチェックを行う。
続いて、氏名をキーに重複チェックする。
すでに存在している場合は更新(UPDATE文)を行い、そうでない場合は登録(INSERT文)としている。
UPDATE文は次のように書く。
UPDATE テーブル名 SET (カラム1)=(値1), (カラム2)=(値2),... WHERE (条件);更新するレコードを一意に絞るためにWHERE句の条件が重要となる。ここでは、ユニーク番号である ID番号 を条件にした。
生年月日は YYYYMMDD 形式のテキストだが、生年しか分からない出演者もいるので、月や日を '00' にしたり '??' にすることができるようにしてある。
解説:年齢計算
averageAge.php
206: /**
207: * 指定した生年月日の現在の年齢を計算する.
208: * @param string $birthday 生年月日(yyyymmdd)
209: * @return float 年齢
210: */
211: function getAge($birthday) {
212: preg_match('/([0-9]{4})([0-9]{2})?([0-9]{2})?/i', $birthday, $arr);
213: $yy = isset($arr[1]) ? date('Y') - $arr[1] : 0;
214: $mm = isset($arr[2]) ? date('n') - $arr[2] : 0;
215: $dd = isset($arr[3]) ? date('j') - $arr[3] : 0;
216:
217: return ($dd / 30 + $mm) / 12 + $yy;
218: }
月日まで含め、年齢を小数点計算している。
また、前述のように生年しか分からない出演者もいるので、その場合は、月や日をゼロとして計算している。
解説:出演者一覧をTABLEタグに整形
averageAge.php
284: /**
285: * 出演者一覧をHTMLタグ(TABLE)に整形する.
286: * @param array $users 出演者一覧
287: * @param int $id 編集したいID
288: * @return array(TABLEタグ, 出力テキスト)
289: */
290: function users2table($users, $id) {
291: //登録ボタンの要否
292: $button = ' ';
293: foreach ($users as $user) {
294: if (($user['birthday'] == '') || ($id == $user['id'])) {
295: $button =<<< EOT
296: <input type="submit" id="insert" name="insert" value="登録">
297:
298: EOT;
299: break;
300: }
301: }
302:
303: //TABLE作成
304: $flag = TRUE;
305: $message = '';
306: $table =<<< EOT
307: <table class="stripe_table">
308: <tr>
309: <th>氏名</th>
310: <th>生年月日</th>
311: <th>年齢</th>
312: <th>{$button}</th>
313: </tr>
314:
315: EOT;
316: $cnt = 0;
317: $n = 0;
318: $total = 0;
319: foreach ($users as $user) {
320: //入力・編集行
321: if (($user['birthday'] == '') || ($id == $user['id'])) {
322: $flag = FALSE;
323: $message = '';
324: $table .=<<< EOT
325: <tr>
326: <td><input type="text" name="names[{$cnt}]" size="10" value="{$user['name']}"></td>
327: <td><input type="text" name="birthdays[{$cnt}]" size="12" value="{$user['birthday']}"></td>
328: <td> </td>
329: <td><input type="hidden" name="ids[{$cnt}]" id="ids[{$cnt}]" value="{$user['id']}"></td>
330: </tr>
331:
332: EOT;
333: $cnt++;
334:
335: //表示行
336: } else {
337: $birth = formatBirthday($user['birthday']);
338: $age = getAge($user['birthday']);
339: $total += $age;
340: $age = sprintf('%.1f', $age);
341: $table .=<<< EOT
342: <tr>
343: <td>{$user['name']}</td>
344: <td style="text-align:center;">{$birth}</td>
345: <td style="text-align:center;">{$age}</td>
346: <td style="text-align:center;"><button type="submit" name="edit" value="{$user['id']}">編集</button></td>
347: </tr>
348:
349: EOT;
350: $message .= sprintf(MESSAGE_BODY, $user['name'], floor($age));
351: $n++;
352: }
353: }
354:
355: //平均年齢
356: if ($flag) {
357: $average = sprintf('%.1f', $total / $n);
358: $table .=<<< EOT
359: <tr>
360: <td> </td>
361: <td style="text-align:right;">平均年齢</td>
362: <td style="text-align:center;">{$average}</td>
363: <td> </td>
364: </tr>
365:
366: EOT;
367: $message = sprintf(MESSAGE_HEAD, $average) . $message;
368: }
369:
370: $table .=<<< EOT
371: </table>
372:
373: EOT;
374:
375: $message .= MESSAGE_TAIL;
376:
377: return array($table, $message);
378: }
次に、出演者の一覧(氏名・生年月日・年齢)と編集ボタンを生成する。
生年月日が空文字なら新規登録が必要。引数のIDと一致していたら、その出演者の情報を編集する必要があるとみなす。
inputタグ の名前やIDとして配列を用いることで、複数の出演者を一気に登録できるようにしている。
参考サイト
- SQLiteを用意する:ぱふぅ家のホームページ
- PHP+SQLite:最大/最小値・平均値を求める:ぱふぅ家のホームページ
毎週日曜日の朝、TBS系『サンデーモーニング』を見ているのだが、出演者の年齢が気になり、いちいち調べてツイートしている。
そこで今回は、あらかじめ出演者の氏名と生年月日をデータベースに登録しておき、その日の年齢を計算で求めるPHPプログラムを作ってみることにする。
出演者は毎週入れ替わっており、簡単に追加や編集が行える機能も備えることにする。
あわせて、これまで取り上げなかったデータベースの更新を行う UPDATE文について解説する。
(2024年4月7日)データベース登録時の不具合を解決
(2024年2月4日)名前の区切り文字に空白を追加,pahooInputData対応