fw 説明書(書きかけ)

特徴

本格的なフレームワークを導入するほどの規模ではないが、それなりに多機能なのである程度の処理は任せたい。今更PHP4に対応させる必要があるが、PHP5でも動作させる必要がある。本番環境のテストが公開直前までできないので、ひととおり内部を見渡せる軽量なフレームワークが好ましい。ついでに、簡易なデータベース管理ツールも欲しい。

…という場合に役に立つかもしれません。というか、そんなことがあったために自作することにした。_(:3 」∠ )_

概要

大まかな処理の流れは以下のとおりです。(何らかのフレームワークを使ったことがある人向けの説明。)

問題点

システム要件

ライセンス

The MIT License

インストール

開発用

すべてのファイルを公開ディレクトリ内に配置する方法です。

public_html / config.php
     |        index.php
     |
     +-- app /
     |
     +-- libs /

ブラウザで index.php にアクセスし、「About Framework」というページが表示されれば成功です。

本番用

一部のファイルを公開ディレクトリ外に配置する方法です。

home / config.php
  |
  +-- public_html / index.php
  |
  +-- app /
  |
  +-- libs /

この場合、index.php にある

require_once 'config.php';

を以下のように修正します。(index.php から見た config.php へのパス。)

require_once '../config.php';

さらに config.php にある

define('MAIN_LIBRARY_PATH', '');
define('MAIN_APPLICATION_PATH', '');

を以下のように修正します。(index.php から見た libs/ へのパスと app/ へのパス。)

define('MAIN_LIBRARY_PATH', '../');
define('MAIN_APPLICATION_PATH', '../');

ブラウザで index.php にアクセスし、「About Framework」というページが表示されれば成功です。

なお、本番環境では config.php にある

define('DEBUG_LEVEL', 1);

この部分を、必ず 0 に設定しておいてください。(データベース管理ツールなどに不正にアクセスされる可能性があります。)

入門

MVCフレームワーク

このフレームワークは処理をMVC(Model-View-Controller)に分割して管理します。

Model(モデル)

モデルはデータを扱う部品です。データの検索、変換、検証、登録などを行います。

View(ビュー)

ビューはデータの表示を担当します。主にHTMLのテンプレートを扱います。

Controller(コントローラ)

コントローラはユーザからのリクエストを扱います。モデルとビューの助けを借りてユーザに結果を返します。

ディレクトリ構成

app/ 内に作成したいプログラムのファイルを格納します。

libs/cores/ 内にフレームワークのコアファイルが可能されています。この内容は編集する必要はありません。

libs/plugins/ 内に自分で作成した共通関数のファイルを格納します。

URLリライティング

フレームワークを呼び出すURLは通常 http://www.example.com/index.php/test1/test2 のような形式ですが、mod_rewrite を使用すれば http://www.example.com/test1/test2 のような形式にすることができます。

index.php と同じディレクトリ内に .htaccess ファイルを作成し、以下の内容を記載します。

DirectoryIndex index.php

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule (.*) index.php/$1
</IfModule>

さらに、config.php にある以下の部分を修正します。

define('MAIN_FILE', $_SERVER['SCRIPT_NAME']);

この部分を、以下のように修正します。

define('MAIN_FILE', dirname($_SERVER['SCRIPT_NAME']));

規約

コントローラの規約

コントローラは app/controllers/ 内に格納します。

URLで指定された2つの値に従ってコントローラが読み込まれます。(3つ目以降の値を指定しても、コントローラの呼び出しに影響しません。)値が省略されると default が省略されたものとみなされます。

コントローラ内で連想配列 $view に値を格納すると、ビューから参照することができます。

モデルの規約

モデルは app/models/ 内に格納します。

データベースのテーブル名とモデルのファイル名は一致させます。名前は複数形を推奨します。

例えば members テーブルのモデルは app/models/members.php となります。ファイルを作成することにより、select_members()insert_members()update_members()delete_members() などの命令が使えるようになります。これらはモデル内に以下のような関数を定義することにより、各機能を上書きすることができます。(必要に応じて、さらにコードを追加します。)

function select_members($queries)
{
  $queries['from'] = DATABASE_PREFIX . 'members';
  $results = db_select($queries);
  return $results;
}

function insert_members($queries)
{
  $queries['insert_into'] = DATABASE_PREFIX . 'members';
  $resource = db_insert($queries);
  return $resource;
}

function update_members($queries)
{
  $queries['update'] = DATABASE_PREFIX . 'members';
  $resource = db_update($queries);
  return $resource;
}

function delete_members($queries)
{
  $queries['delete_from'] = DATABASE_PREFIX . 'members';
  $resource = db_delete($queries);
  return $resource;
}

function normalize_members($queries)
{
  return $queries;
}

function validate_members($queries)
{
  return array();
}

ビューの規約

ビューは app/views/ 内に格納します。

URLで指定された2つの値に従ってコントローラが読み込まれます。値が省略されると default が省略されたものとみなされます。

コントローラ内で連想配列 $view に値を格納すると、ビューから参照することができます。

コアライブラリ

フレームワークの動作に必要な命令です。フレームワーク内部で呼び出されていますが、自分で書くプログラムに流用することもできます。

コアライブラリによって提供される命令の一部をフレームワークから提供される関数で紹介しています。

プラグイン

自分で追加することができる命令です。

プラグインは libs/plugins/ 内に格納されています。

はじめから用意されているプラグインによって提供される命令の一部をフレームワークから提供されるプラグインで紹介しています。

情報表示

http://www.example.com/index.php?mode=info_fw へアクセスすると、フレームワークの情報が表示されます。表示されない場合、config.php の以下の部分を 12 に設定してください。(本番環境では 0 にしておくことを推奨します。)

define('DEBUG_LEVEL', 0);

データベース

config.php にある、以下の部分でデータベースの接続設定を行います。データベースを使用しない場合、設定の必要はありません。

define('DATABASE_TYPE', '');  //pdo_mysql or pdo_pgsql or pdo_sqlite or pdo_sqlite2 or mysql or pgsql or sqlite
define('DATABASE_HOST', '');
define('DATABASE_PORT', '');
define('DATABASE_USERNAME', '');
define('DATABASE_PASSWORD', '');
define('DATABASE_NAME', '');
define('DATABASE_PREFIX', '');

上から「接続方法」「ホスト」「ポート番号」「ユーザー名」「パスワード」「データベース名」「テーブル名のプレフィックス」です。

SQLiteを使用する場合、「データベース名」はデータベースファイルへのパスを設定します。例えば DATABASE_NAMEdb/fw.db と設定した場合、index.php と階層に db ディレクトリを作成してパーミッションを 707 に設定し、さらにその中に fw.db を作成してパーミッションを 606 に設定します。

PDOでMySQLへ接続する場合、一例ですが以下のように設定します。

define('DATABASE_TYPE', 'pdo_mysql');  //pdo_mysql or pdo_pgsql or pdo_sqlite or pdo_sqlite2 or mysql or pgsql or sqlite
define('DATABASE_HOST', 'localhost');
define('DATABASE_PORT', '');
define('DATABASE_USERNAME', 'root');
define('DATABASE_PASSWORD', '1234');
define('DATABASE_NAME', 'fw');
define('DATABASE_PREFIX', '');

PDOでPostgreSQLへ接続する場合、一例ですが以下のように設定します。

define('DATABASE_TYPE', 'pdo_pgsql');  //pdo_mysql or pdo_pgsql or pdo_sqlite or pdo_sqlite2 or mysql or pgsql or sqlite
define('DATABASE_HOST', 'localhost');
define('DATABASE_PORT', '');
define('DATABASE_USERNAME', 'root');
define('DATABASE_PASSWORD', '1234');
define('DATABASE_NAME', 'fw');
define('DATABASE_PREFIX', '');

PDOでSQLite3へ接続する場合、一例ですが以下のように設定します。

define('DATABASE_TYPE', 'pdo_sqlite');  //pdo_mysql or pdo_pgsql or pdo_sqlite or pdo_sqlite2 or mysql or pgsql or sqlite
define('DATABASE_HOST', '');
define('DATABASE_PORT', '');
define('DATABASE_USERNAME', '');
define('DATABASE_PASSWORD', '');
define('DATABASE_NAME', 'db/fw.db');
define('DATABASE_PREFIX', '');

PDOでSQLite2へ接続する場合、一例ですが以下のように設定します。

define('DATABASE_TYPE', 'pdo_sqlite2');  //pdo_mysql or pdo_pgsql or pdo_sqlite or pdo_sqlite2 or mysql or pgsql or sqlite
define('DATABASE_HOST', '');
define('DATABASE_PORT', '');
define('DATABASE_USERNAME', '');
define('DATABASE_PASSWORD', '');
define('DATABASE_NAME', 'db/fw.db');
define('DATABASE_PREFIX', '');

mysql関数で接続する場合、一例ですが以下のように設定します。

define('DATABASE_TYPE', 'mysql');  //pdo_mysql or pdo_pgsql or pdo_sqlite or pdo_sqlite2 or mysql or pgsql or sqlite
define('DATABASE_HOST', 'localhost');
define('DATABASE_PORT', '');
define('DATABASE_USERNAME', 'root');
define('DATABASE_PASSWORD', '1234');
define('DATABASE_NAME', 'fw');
define('DATABASE_PREFIX', '');

pg関数で接続する場合、一例ですが以下のように設定します。

define('DATABASE_TYPE', 'pgsql');  //pdo_mysql or pdo_pgsql or pdo_sqlite or pdo_sqlite2 or mysql or pgsql or sqlite
define('DATABASE_HOST', 'localhost');
define('DATABASE_PORT', '');
define('DATABASE_USERNAME', 'root');
define('DATABASE_PASSWORD', '1234');
define('DATABASE_NAME', 'fw');
define('DATABASE_PREFIX', '');

sqlite関数で接続する場合、一例ですが以下のように設定します。

define('DATABASE_TYPE', 'sqlite');  //pdo_mysql or pdo_pgsql or pdo_sqlite or pdo_sqlite2 or mysql or pgsql or sqlite
define('DATABASE_HOST', '');
define('DATABASE_PORT', '');
define('DATABASE_USERNAME', '');
define('DATABASE_PASSWORD', '');
define('DATABASE_NAME', 'db/fw.db');
define('DATABASE_PREFIX', '');

文字コード

config.php にある、以下の部分でデータベースの文字コード設定を行います。

MySQL使用時に DATABASE_CHARSET を指定すると、「'SET NAMES ' . DATABASE_CHARSET」でデータベースの文字コードが設定されます。

データベースへ入力する際、DATABASE_CHARSET_INPUT_FROM から DATABASE_CHARSET_INPUT_TO に文字コードが変換されます。同じ値が指定されている場合、何も行われません。

データベースから出力する際、DATABASE_CHARSET_OUTPUT_FROM から DATABASE_CHARSET_OUTPUT_TO に文字コードが変換されます。同じ値が指定されている場合、何も行われません。

define('DATABASE_CHARSET', 'UTF8');  //for mysql, set names.
define('DATABASE_CHARSET_INPUT_FROM', 'UTF-8');  //for php function.
define('DATABASE_CHARSET_INPUT_TO', 'UTF-8');  //for php function.
define('DATABASE_CHARSET_OUTPUT_FROM', 'UTF-8');  //for php function.
define('DATABASE_CHARSET_OUTPUT_TO', 'UTF-8');  //for php function.

データベースの文字コードが SJIS で表示の際に EUC-JP を使う場合、一例ですが以下のように指定します。

define('DATABASE_CHARSET', 'SJIS');  //for mysql, set names.
define('DATABASE_CHARSET_INPUT_FROM', 'auto');  //for php function.
define('DATABASE_CHARSET_INPUT_TO', 'SJIS');  //for php function.
define('DATABASE_CHARSET_OUTPUT_FROM', 'auto');  //for php function.
define('DATABASE_CHARSET_OUTPUT_TO', 'EUC-JP');  //for php function.

管理画面

http://www.example.com/index.php?mode=db_admin へアクセスすると、データベースの管理画面が表示されます(データベース使用時のみ)。表示されない場合、config.php の以下の部分を 12 に設定してください。

define('DEBUG_LEVEL', 0);

セッション

セッションは常に開始されているので、通常のPHPプログラムと同様に $_SESSION でセッションを読み書きします。ただし $_SESSION['core'] はフレームワーク内部で使用しているので、この値を上書きしたりしないように注意してください。

デバッグ

config.php の以下の部分を 1 に設定すると、エラー発生時に詳細が表示されます。また、データベース管理ツールなども利用できるようになります。2 に設定すると、さらに実行したSQLがその都度画面に表示されるようになります。

define('DEBUG_LEVEL', 0);

リファレンス

フレームワークから提供される関数

import()

ファイルを読み込みます。

import('app/config.php')  //「app/config.php」を読み込む
import('app/views/header.php')  //「app/views/header.php」を読み込む

convert()

文字コードを変換します。

$converted = convert($data, 'UTF-8', 'EUCJP-WIN');  //「EUCJP-WIN」から「UTF-8」に変換

token()

ワンタイムトークンを扱います。token('create') でトークンを発行し、フォームなどから送信し、$_REQUEST['token'] で受信して、token('check') で確認できます。

$token = token('create')  //ワンタイムトークンを発行
$flag = token('check')  //ワンタイムトークンを確認

redirect()

リダイレクトします。

redirect('http://www.example.com/sample.html')  //外部ページへリダイレクト
redirect('/message/list')  //内部ページヘリダイレクト

localdate()

日時を取得します。

localdate()  //タイムスタンプを取得
localdate('Y-m-d H:i:s')  //フォーマットして取得
localdate('Y年m月d日 H時i分s秒', 1375335520)  //タイムスタンプからフォーマットして取得
localdate('Y年m月d日 H時i分s秒', '2013-08-01 14:38:40')  //文字列からフォーマットして取得

この関数の時差は config.phpMAIN_TIME で調整出来ます。(date_default_timezone_set() など、他の方法でも可能。)

t()

入力欄用のテキストを作成&出力します。(htmlspecialchars() が適用されます。)

t('テキスト')  //テキストを出力
$text = t('テキスト', true)  //テキストを作成

h()

表示用のテキストを作成&出力します。(htmlspecialchars()nl2br() が適用されます。)

h('テキスト')  //テキストを出力
$html = h('テキスト', true)  //テキストを作成

logging()

ログを記録します。記録先は config.phpLOGGING_FILE で指定します。

logging('記録したいメッセージ')

db_query()

db_result()

db_count()

db_escape()

db_error()

db_transaction()

db_commit()

db_rollback()

フレームワークから提供されるプラグイン

file.php

ファイルを扱う命令が提供されます。

file_info()
file_mimetype()

directory.php

ディレクトリを扱う命令が提供されます。

directory_info()
directory_mkdir()
directory_rmdir()

mail.php

メールを扱う命令が提供されます。

mail_send()

ui.php

ユーザーインターフェースを扱う命令が提供されます。

ui_pager()

チュートリアル

ブログチュートリアル

簡易な記事管理システムを例に、実際の作成手順を紹介します。ここでは、PHP5+MySQLで作成するものとします。また、作成するファイルの文字コードはすべてUTF-8Nとします。

インストール

任意のディレクトリにフレームワークを配置します。

データベースの作成

phpMyAdminなどのデータベース管理ツールから、必要に応じてデータベースを作成します。

CREATE DATABASE fw;
USE fw;

テーブルを作成します。

CREATE TABLE posts(
  id        INT UNSIGNED NOT NULL AUTO_INCREMENT,
  created   DATETIME     NOT NULL,
  modified  DATETIME     NOT NULL,
  title     VARCHAR(255),
  body      TEXT,
  PRIMARY KEY(id)
);

ダミーデータを登録します。

INSERT INTO posts VALUES(NULL, NOW(), NOW(), 'テスト1', 'これはテスト1です。');
INSERT INTO posts VALUES(NULL, NOW(), NOW(), 'テスト2', 'これはテスト2です。');
INSERT INTO posts VALUES(NULL, NOW(), NOW(), 'テスト3', 'これはテスト3です。');

データベースの設定

config.php を編集します。

define('DATABASE_TYPE', 'pdo_mysql');  //pdo_mysql or pdo_pgsql or pdo_sqlite or pdo_sqlite2 or mysql or pgsql or sqlite
define('DATABASE_HOST', 'localhost');
define('DATABASE_PORT', '');
define('DATABASE_USERNAME', 'root');
define('DATABASE_PASSWORD', '1234');
define('DATABASE_NAME', 'fw');
define('DATABASE_PREFIX', '');

モデルの作成

app/models/posts.php を作成します。ひとまずファイルの内容はカラで大丈夫です。これにより、以下の命令が使えるようになります。

select_posts()
データを取得する命令です。
insert_posts()
データを登録する命令です。
update_posts()
データを編集する命令です。
delete_posts()
データを削除する命令です。
normalize_posts()
データを整理する命令です。
validate_posts()
データを検証する命令です。

コントローラの作成

app/controllers/posts/list.php を作成し、以下の内容を入力します。select_posts()app/models/posts.php を作成することにより使えるようになった関数です。

<?php

$view['posts'] = select_posts(array(
  'order_by' => 'id DESC',
  'limit'    => 10
));

?>

ビューの作成

app/views/posts/list.php を作成し、以下の内容を入力します。

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=<?php t(MAIN_CHARSET) ?>" />
    <title>ブログ</title>
  </head>
  <body>
    <h1>ブログ</h1>
    <table>
      <tr>
        <th>ID</th>
        <th>登録日時</th>
        <th>修正日時</th>
        <th>タイトル</th>
      </tr>
      <?php foreach ($view['posts'] as $post) : ?>
      <tr>
        <td><?php h($post['id']) ?></td>
        <td><?php h(localdate('Y/m/d H:i', $post['created'])) ?></td>
        <td><?php h(localdate('Y/m/d H:i', $post['modified'])) ?></td>
        <td><?php h($post['title']) ?></td>
      </tr>
      <?php endforeach ?>
    </table>
  </body>
</html>

動作確認

index.php/posts/list にアクセスし、記事一覧が表示されれば成功です。

引き続き機能を実装していきます。

記事の個別表示

app/controllers/posts/view.php を作成し、以下の内容を入力します。

<?php

$posts = select_posts(array(
  'where' => 'id = ' . db_escape($_GET['id'])
));
if (empty($posts)) {
  warning('記事が見つかりません。');
} else {
  $view = $posts[0];
}

?>

app/views/posts/view.php を作成し、以下の内容を入力します。

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=<?php t(MAIN_CHARSET) ?>" />
    <title>ブログ</title>
  </head>
  <body>
    <h1>ブログ</h1>
    <table>
      <tr>
        <th>ID</th>
        <td><?php h($view['id']) ?></td>
      </tr>
      <tr>
        <th>登録日時</th>
        <td><?php h(localdate('Y/m/d H:i', $view['created'])) ?></td>
      </tr>
      <tr>
        <th>修正日時</th>
        <td><?php h(localdate('Y/m/d H:i', $view['modified'])) ?></td>
      </tr>
      <tr>
        <th>タイトル</th>
        <td><?php h($view['title']) ?></td>
      </tr>
      <tr>
        <th>本文</th>
        <td><?php h($view['body']) ?></td>
      </tr>
    </table>
  </body>
</html>

app/views/posts/list.php にある

<td><?php h($post['title']) ?></td>

この部分を以下のように修正します。

<td><a href="<?php t(MAIN_FILE) ?>/posts/view?id=<?php t($post['id']) ?>"><?php h($post['title']) ?></a></td>

記事一覧の各タイトルから、個別表示ページヘリンクされます。

記事の追加

app/views/posts/add.php を作成し、以下の内容を入力します。

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=<?php t(MAIN_CHARSET) ?>" />
    <title>ブログ</title>
  </head>
  <body>
    <h1>ブログ</h1>
    <form action="<?php t(MAIN_FILE) ?>/posts/add" method="post">
      <fieldset>
        <legend>登録フォーム</legend>
        <dl>
          <dt>タイトル</dt>
            <dd><input type="text" name="title" size="30" value="" /></dd>
          <dt>本文</dt>
            <dd><textarea name="body" rows="10" cols="50"></textarea></dd>
        </dl>
        <p><input type="submit" value="登録する" /></p>
      </fieldset>
    </form>
  </body>
</html>

app/controllers/posts/add.php を作成し、以下の内容を入力します。

<?php

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  $resource = insert_posts(array(
    'values' => array(
      'created'  => db_escape(localdate('Y-m-d H:i:s')),
      'modified' => db_escape(localdate('Y-m-d H:i:s')),
      'title'    => db_escape($_POST['title']),
      'body'     => db_escape($_POST['body'])
    )
  ));
  if (!$resource) {
    error('データを登録できません。');
  }

  redirect('/posts/list');
}

?>

app/views/posts/list.php に以下のコードを追加します。

<p><a href="<?php t(MAIN_FILE) ?>/posts/add">新規登録</a></p>

index.php/posts/list にアクセスすると「新規登録」リンクが表示されるので、そこから記事を投稿できます。

データのバリデーション

app/models/posts.php に以下の内容を入力します。

<?php

function validate_posts($queries)
{
  $messages = array();

  //タイトル
  if (isset($queries['title'])) {
    if ($queries['title'] == '') {
      $messages[] = 'タイトルが入力されていません。';
    } elseif (mb_strlen($queries['title'], MAIN_INTERNAL_ENCODING) > 20) {
      $messages[] = 'タイトルは20文字以内で入力してください。';
    }
  }

  //本文
  if (isset($queries['body'])) {
    if (mb_strlen($queries['body'], MAIN_INTERNAL_ENCODING) > 1000) {
      $messages[] = '本文は1000文字以内で入力してください。';
    }
  }

  return $messages;
}

?>

app/controllers/posts/add.php

if ($_SERVER['REQUEST_METHOD'] == 'POST') {

この直後に以下の内容を追加します。

$warnings = validate_posts($_POST);
if (!empty($warnings)) {
  warning($warnings);
}

例えばタイトルを空欄のままで記事を投稿しようとすると、エラーが表示されるようになります。

記事の編集

app/views/posts/edit.php を作成し、以下の内容を入力します。

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=<?php t(MAIN_CHARSET) ?>" />
    <title>ブログ</title>
  </head>
  <body>
    <h1>ブログ</h1>
    <form action="<?php t(MAIN_FILE) ?>/posts/edit" method="post">
      <fieldset>
        <legend>編集フォーム</legend>
        <input type="hidden" name="id" value="<?php t($view['id']) ?>" />
        <dl>
          <dt>タイトル</dt>
            <dd><input type="text" name="title" size="30" value="<?php t($view['title']) ?>" /></dd>
          <dt>本文</dt>
            <dd><textarea name="body" rows="10" cols="50"><?php t($view['body']) ?></textarea></dd>
        </dl>
        <p><input type="submit" value="編集する" /></p>
      </fieldset>
    </form>
  </body>
</html>

app/controllers/posts/edit.php を作成し、以下の内容を入力します。

<?php

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  $warnings = validate_posts($_POST);
  if (!empty($warnings)) {
    warning($warnings);
  }

  $resource = update_posts(array(
    'sets' => array(
      'modified' => db_escape(date('Y-m-d H:i:s')),
      'title'    => db_escape($_POST['title']),
      'body'     => db_escape($_POST['body'])
    ),
    'where' => 'id = ' . db_escape($_POST['id'])
  ));
  if (!$resource) {
    error('データを編集できません。');
  }

  redirect('/posts/list');
} else {
  $posts = select_posts(array(
    'where' => 'id = ' . db_escape($_GET['id'])
  ));
  if (empty($posts)) {
    warning('記事が見つかりません。');
  } else {
    $view = $posts[0];
  }
}

?>

app/views/posts/list.php にある

<td><a href="<?php t(MAIN_FILE) ?>/posts/view?id=<?php t($post['id']) ?>"><?php h($post['title']) ?></a></td>

この部分を以下のように修正します。

<td>
  <a href="<?php t(MAIN_FILE) ?>/posts/view?id=<?php t($post['id']) ?>"><?php h($post['title']) ?></a>
  <a href="<?php t(MAIN_FILE) ?>/posts/edit?id=<?php t($post['id']) ?>">編集</a>
</td>

記事一覧の「編集」リンクから、記事を編集できるようになります。

記事の削除

app/controllers/posts/delete.php を作成し、以下の内容を入力します。

<?php

$resource = delete_posts(array(
  'where' => 'id = ' . db_escape($_GET['id'])
));
if (!$resource) {
  error('データを削除できません。');
}

redirect('/posts/list');

?>

app/views/posts/list.php にある

<td>
  <a href="<?php t(MAIN_FILE) ?>/posts/view?id=<?php t($post['id']) ?>"><?php h($post['title']) ?></a>
  <a href="<?php t(MAIN_FILE) ?>/posts/edit?id=<?php t($post['id']) ?>">編集</a>
</td>

この部分を以下のように修正します。

<td>
  <a href="<?php t(MAIN_FILE) ?>/posts/view?id=<?php t($post['id']) ?>"><?php h($post['title']) ?></a>
  <a href="<?php t(MAIN_FILE) ?>/posts/edit?id=<?php t($post['id']) ?>">編集</a>
  <a href="<?php t(MAIN_FILE) ?>/posts/delete?id=<?php t($post['id']) ?>">削除</a>
</td>

記事一覧の「削除」リンクから、記事を削除できるようになります。

その他の機能追加

ワンタイムトークンの実装
token() でトークンの発行と確認ができます。
ファイルアップロードの実装
専用の機能は無いので、通常の方法でアップロードします。
ユーザー認証の実装
専用の機能は無いので、セッションを使うなどして実装します。
adminルーティング
専用の機能は無いので、管理ページ用のコントローラを作成するなどして実装します。
レイアウト
専用の機能は無いので、import() で共通テンプレートを読み込むなどします。
フィルター
専用の機能は無いので、共通関数を定義するなどします。
アソシエーション
専用の機能は無いので、db_select()db_query() でSQLを発行するなどします。