levis: PHP Framework 説明書

エトセトラ

目次に戻る

補足

データベースを使わないモデル

バリデーションだけを使う場合などは、通常の手順で任意の名前のモデルを作成します。ただしデータベースを扱う命令を呼び出すとエラーになります。

ファイルアップロードの実装

専用の機能は無いので、通常の方法でアップロードします。

ユーザー認証の実装

専用の機能は無いので、セッションを使うなどして実装します。

レイアウト

専用の機能は無いので、import() で共通テンプレートを読み込むなどします。

フィルター

専用の機能は無いので、共通関数を定義するなどします。

ORM・アソシエーション

専用の機能は無いので、SQLの発行には db_select()select_xxx() などを使います。

アソシエーションのように関連データを取得したい場合、モデル内で db_select()db_query() などによってSQLを発行するなどします。

外部JSファイルでプログラムのパスを取得する

一例ですが、PHPプログラム側で

$GLOBALS['config']['http_path'] = '/path/to/app/';

このようにパスを定義しておき、共通のビューで常に

<script>
var HTTP_PATH = '<?php t($GLOBALS['config']['http_path']) ?>';
</script>

このような変数もしくは定数を定義し、その後外部JSファイルを読み込むことでパスを参照できます。

本番環境・検収環境・開発環境

Gitなどでソースコードを管理している場合、本番環境・検収環境・開発環境・作業環境(ローカル)の切り替えは一例ですが以下の手順で対応できます。

  1. 本番環境か否かでプログラムを分岐させたい場合、​PRODUCTION という定数が定義済みか否かで判定する。同様に、検収環境か否かは ​STAGING という定数で判定する。同様に、開発環境か否かは ​DEVELOP という定数で判定する。どれも定義されていなければ作業環境とする。
  2. フレームワークの config.phpconfig.default.php という名前で保存しておき、環境に依存しない内容(作業環境用)のみ設定しておく。config.php はリポジトリの管理対象外にしておく。
  3. 本番環境、検収環境、開発環境は通常1つなので、それらの設定を config.production.phpconfig.staging.phpconfig.develop.php として作成済みにしておき、それらの変更内容もgitで管理する。(git内に本番環境の情報を含めていい場合のみ。)
  4. 作業環境でプログラムを動作させる場合、各々の環境で config.default.php を複製して config.php を作成し、環境に応じた設定を行う。
  5. git内に本番環境の情報を含めていい場合、本番環境や検収環境や開発環境では config.production.phpconfig.staging.phpconfig.develop.php を複製して config.php を作成する。config.php は直接編集せず、config.production.phpconfig.staging.php に変更があった場合に複製し直す。
    git内に本番環境の情報を含めてはいけない場合、本番環境や検収環境や開発環境でも config.default.php を複製して config.php を作成し、環境に応じた設定を行う。
    いずれの場合でも、本番環境なら ​PRODUCTION という定数が、検収環境なら ​STAGING という定数が、開発環境なら ​DEVELOP という定数が config.php 内で定義済みになるようにしておく。

予約語一覧

以下の語句からはじまる定数。

以下の変数。

以下のリクエスト変数。

以下のグローバル変数。

以下のセッション変数。

以下の関数。

以下の語句からはじまる関数。

ワンタイムトークンの実装

CSRF対策に、ワンタイムトークンを実装する方法です。ワンタイムトークンを扱うための命令は標準で用意されているので、これを呼び出すことで対応できます。

まずはコントローラで

$view['token'] = token('create')

このようにすると、トークンの発行ができます。これをビューで

<input type="hidden" name="_token" value="<?php t($view['token']) ?>" />

このようにしてフォームタグに埋め込み、トークンを送信します。トークンを送信する際の名前は _token で固定されています。送られたトークンはコントローラで

if (!token('check')) {
    error('不正なアクセスです。');
}

このようにして確認します。

同時に複数のトークンを扱う

一度トークンを発行しても、再度トークンを発行すると以前のトークンは失われてしまいます。同時に複数のトークンを扱いたい場合、

$view['token'] = token('create', '任意の名前')

このようにトークンを発行し、

if (!token('check', '任意の名前')) {
    error('不正なアクセスです。');
}

このようにトークンを確認します。トークンを送信するタグは同じで大丈夫です。

ファイルの読み込み先を変更する

グローバル変数 $GLOBALS['_target'] に値を代入すると、import() でファイルを読み込む際の場所として認識されます。これを利用すると「会社単位に提供するASPを作成するが、一部の処理は会社ごとにしたい」のような場合に対応できます。

例えば app/routing.php に以下のように書いておくと、

// 先頭のパラメータを会社IDとみなす
if (isset($_params[0]) && preg_match('/^[\w\-]+$/', $_params[0])) {
    $GLOBALS['_target'] = 'companies/' . $_params[0];

    $_REQUEST['_mode'] = empty($_params[1]) ? 'home'  : $_params[1];
    $_REQUEST['_work'] = empty($_params[2]) ? 'index' : $_params[2];
}

/test/home/index にアクセスしたときは /companies/test/app/ 内のMVCが使われるようになり、/sample/home/index にアクセスしたときは /companies/sample/app/ 内のMVCが使われるようになります。

このとき、/companies/test/app/ 内のMVCで

import('app/views/header.php');

と書くと

/companies/test/app/views/header.php

が読み込まれるようになります。このファイルが存在しなければ、通常通り /app/views/header.php が読み込まれます。

adminルーティングとサブドメインルーティング

グローバル変数 $GLOBALS['_routing'] に値を代入すると、コントローラとビューのファイル読み込み先を変更できます。(モデルの呼び出しには影響しません。)

これを利用して app/routing.php に以下の処理を書くと、URLルーティングのルールを変更できます。これで index.php/admin/param2/param3 でアクセスした時、param2param3 の値によって admin 内のコントローラとビューが呼び出されるようになります。

<?php

if (isset($_params[0]) && preg_match('/^admin$/', $_params[0])) {
    $GLOBALS['_routing'] = $_params[0];

    $_REQUEST['_mode'] = empty($_params[1]) ? 'home'  : $_params[1];
    $_REQUEST['_work'] = empty($_params[2]) ? 'index' : $_params[2];
}

具体的には、

となります。管理ページが非常に多機能な場合など、処理をフォルダごとに分けることができるようになります。admin の部分などを変更すれば、ルーティングのルールは自由に変更できます。app/routing.php の詳細については、フレームワーク本体の処理内容に手を加えるを参照してください。

サブドメインの値をもとにルーティングのルールを変更する例も紹介します。

if (isset($_SERVER['SERVER_NAME']) && preg_match('/^(test|sample)\./', $_SERVER['SERVER_NAME'], $matches)) {
    $GLOBALS['_routing'] = $matches[1];
} else {
    $GLOBALS['_routing'] = 'www';
}

この場合、

となります。サブドメインをまたいだプログラムを作る場合でも、同じフレームワークで一元管理できるようになります。

PHPのセッションをデータベースで管理する

PHPのセッションは、デフォルトでは /var/lib/php/session 内にファイルとして保存されます。基本的にはこれで問題ないですが、サーバが複数台構成の場合など何らかの理由でデータベースに保存したいことがあります。

通常のPHPプログラムと同じ手法で対応することができますが、MySQLを例に具体的な内容を紹介します。(参考: How do I store sessions in a database?

まずはデータベースに、セッション保存用のテーブルを作成します。

CREATE TABLE levis_sessions(
    id       VARCHAR(255),
    created  DATETIME,
    modified DATETIME,
    data     LONGBLOB,
    PRIMARY KEY(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT 'session';

libs/vendors/SessionDatabase.php を作成して以下を記述します。

<?php

class SessionDatabase
{
    private $db_dns;
    private $db_username;
    private $db_password;
    private $db_table;
    private $db;

    public function __construct($db_dns, $db_username, $db_password, $db_table)
    {
        $this->db_dns      = $db_dns;
        $this->db_username = $db_username;
        $this->db_password = $db_password;
        $this->db_table    = $db_table;
    }

    public function open($path, $name)
    {
        try {
            $this->db = new PDO($this->db_dns, $this->db_username, $this->db_password, array(
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true
            ));
        } catch (PDOException $e) {
            echo 'Session Open Error: ' . $e->getMessage();

            return false;
        }

        return true;
    }

    public function close()
    {
        $this->db = null;

        return true;
    }

    public function read($id)
    {
        try {
            $stmt = $this->db->prepare('SELECT data FROM ' . $this->db_table . ' WHERE id = :id');
            $stmt->bindValue(':id', $id);
            $stmt->execute();
            $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
        } catch (PDOException $e) {
            echo 'Session Read Error: ' . $e->getMessage();

            return '';
        }

        if (count($result) > 0 && isset($result[0]['data'])) {
            return $result[0]['data'];
        } else {
            return '';
        }
    }

    public function write($id, $data)
    {
        try {
            $stmt = $this->db->prepare('SELECT data FROM ' . $this->db_table . ' WHERE id = :id');
            $stmt->bindValue(':id', $id);
            $stmt->execute();
            $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
        } catch (PDOException $e) {
            echo 'Session Write Error: ' . $e->getMessage();

            return false;
        }

        try {
            if (count($result) > 0) {
                $stmt = $this->db->prepare('UPDATE ' . $this->db_table . ' SET modified = NOW(), data = :data WHERE id = :id');
                $stmt->bindValue(':data', $data);
                $stmt->bindValue(':id', $id);
            } else {
                $stmt = $this->db->prepare('INSERT INTO ' . $this->db_table . ' VALUES(:id, NOW(), NOW(), :data)');
                $stmt->bindValue(':id', $id);
                $stmt->bindValue(':data', $data);
            }
            $stmt->execute();
        } catch (PDOException $e) {
            echo 'Session Write Error: '.$e->getMessage();

            return false;
        }

        return true;
    }

    public function destroy($id)
    {
        try {
            $stmt = $this->db->prepare('DELETE FROM ' . $this->db_table . ' WHERE id = :id');
            $stmt->bindValue(':id', $id);
            $stmt->execute();
        } catch (PDOException $e) {
            echo 'Session Destroy Error: ' . $e->errorMessage();

            return false;
        }

        return true;
    }

    public function gc($lifetime)
    {
        try {
            $stmt = $this->db->prepare('DELETE FROM ' . $this->db_table . ' WHERE modified < :end');
            $stmt->bindValue(':end', date('Y-m-d H:i:s', time() - $lifetime));
            $stmt->execute();
        } catch (PDOException $e) {
            echo 'Session Garbage Collector Error: '. $e->getMessage();

            return false;
        }

        return true;
    }

    public function __destruct()
    {
        session_write_close();
    }
}

app/bootstrap.php を作成して以下を記述します。(すでにファイルが存在している場合、以下の処理を追加します。)

<?php

// セッションをデータベースで管理
import('libs/vendors/SessionDatabase.php');
$session = new SessionDatabase('mysql:host=' . DATABASE_HOST . ';dbname=' . DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_PREFIX . 'levis_sessions');
session_set_save_handler(
    array($session, 'open'),
    array($session, 'close'),
    array($session, 'read'),
    array($session, 'write'),
    array($session, 'destroy'),
    array($session, 'gc')
);

これでセッションがデータベースに保存されます。

データベースのテーブルロック

モデルでデータベースのテーブルを操作する際、option を渡すとクエリの最後に任意の文字列を指定できます。これにより、例えば以下のように書くことでテーブルロックを行うことができます。

$_view['posts'] = select_posts(array(
    'where'  => 'id >= 1 AND id <= 10',
    'option' => 'FOR UPDATE',
));

テーブルロックのために指定できる文字列や、テーブルロックが使えるかどうかは、使用するデータベースの仕様に依存します。

データベースのトランザクション分離レベルを変更する

MySQLのトランザクション分離レベルはデフォルトで REPEATABLE READ ですが、これを READ COMMITTED に変更する方法です。(設定を変更する権限は与えられているものとします。)

REPEATABLE READ はトランザクション開始後にテーブルの値を変更しても、SELECT で参照できるのは変更前の値です。READ COMMITTED は変更後の値を参照でき、Oracle、PostgreSQL、SQL Server などではデフォルト設定となっています。そちらの方が直感的なので、変更しておくと余計なトラブルを防ぐことができます。

具体的には、app/database.php を作成して以下のコードを記述します。app/database.php の詳細については、フレームワーク本体の処理内容に手を加えるを参照してください。

db_query('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;');

データベースのプレースホルダ

$_view['posts'] = select_posts(array(
    'where' => 'id >= 1 AND id <= 10',
));

このSQLを扱う場合、プレースホルダを使って以下のように書くことができます。(配列で指定した値が、先頭から順に :? へ代入されます。)

$_view['posts'] = select_posts(array(
    'where' => array('id >= :? AND id <= :?', array(1, 10)),
));

以下のように、プレースホルダに文字列を使うこともできます。

$_view['posts'] = select_posts(array(
    'where' => array(
        'id >= :from AND id <= :to',
        array(
            'from' => 1,
            'to'   => 10,
        ),
    ),
));

なお、これは擬似プレースホルダです。同じSQLで値だけ変えて複数実行することはできません。大量のSQLを実行する際に速度アップを図りたい場合、一例ですが以下のようにトランザクションを使用してください。

// トランザクションを開始
db_transaction();

while (/* データ順次取り出し */) {
    // データ内容確認&登録処理
}

if (/* エラーがなければ */) {
    // トランザクションを終了
    db_commit();
} else {
    // エラーがあればロールバック
    db_rollback();
}

登録・編集の際は以下のような特別な書き方を利用できます。(valuesset の内容が、自動的にエスケープされます。)特に理由がなければ、この書き方を推奨します。

$resource = insert_posts(array(
    'values' => array(
        'id'       => $_POST['id'],
        'created'  => $_POST['created'],
        'modified' => $_POST['modified'],
        'title'    => $_POST['title'],
        'body'     => $_POST['body'],
    ),
));
if (!$resource) {
    error('insert error.');
}
$resource = update_posts(array(
    'set' => array(
        'created'  => $_POST['created'],
        'modified' => $_POST['modified'],
        'title'    => $_POST['title'],
        'body'     => $_POST['body'],
    ),
    'where' => array(
        'id = :id',
        array(
            'id' => $_POST['id']
        ),
    ),
));
if (!$resource) {
    error('update error.');
}

上の書き方であえて式を渡したい場合、以下のように配列で渡します。

$resource = update_posts(array(
    'set'   => array(
        'sort' => array('sort + 1'),
    ),
    'where' => array(
        'id = :id',
        array(
            'id' => 2,
        ),
    ),
));

複数のデータベースに接続

db_connect に接続情報を渡すと、別のデータベースに接続できます。

db_connect(array(
    'master' => array(
        'host'     => 'localhost',
        'username' => 'root',
        'password' => '1234',
        'name'     => 'master_db',
    ),
));

$test = db_result(db_query('SELECT * FROM members'));

このように接続した以降は、データベースを扱う命令を呼び出すと master_db データベースが呼ばれるようになります。再度本来のデータベースを呼び出したい場合、

db_connect('default');

を呼び出します。その上で再度 master_db データベースを呼び出したい場合、

db_connect('master');

とすれば呼び出せます。(連想配列のキーの値を指定。)

接続情報として、以下の値を渡せます。値を省略した場合、config.php で指定した値が使われます。

type
接続方法
host
ホスト
port
ポート番号
username
ユーザー名
password
パスワード
name
データベース名
prefix
テーブル名のプレフィックス
charset
データベースの文字コード
charset_input_from
データベースへ入力するときの変換前文字コード
charset_input_to
データベースへ入力するときの変換後文字コード
charset_output_from
データベースから出力するときの変換前文字コード
charset_output_to
データベースから出力するときの変換後文字コード

データベースのクエリエラーを独自に処理する

データベースに渡すクエリに文法ミスがあったりUNIQUEキーの重複があったりする場合、フレームワーク内部でエラー関数が自動的に呼ばれてエラーが表示されます。

これにより処理が強制的に止まりますが、独自のエラー処理を行ったりエラーメッセージを表示したい場合、以下のように処理します。

$resource = db_insert(array(
    'insert_into' => 'categories',
    'values'      => array(
        'id' => 2,  // 重複しているとする
    ),
), false, false);  // 第2・第3引数を false にすると自動でのエラー表示が無効になる

if (!$resource) {
    error('データを登録できません : ' . db_error());  // 任意のエラー処理
}

db_query()db_select()db_insert()db_update()db_delete() で同様の処理を行うことができます。

例えばモデルの insert_categories() などからも同じようにエラー処理を独自に行いたい場合、モデル内で上の関数を呼び出す際に引数を渡すようにします。具体的には

/**
 * 分類の登録
 *
 * @param array $queries
 * @param array $options
 * @param bool  $return
 * @param bool  $error
 *
 * @return resource
 */
function insert_categories($queries, $options = array(), $return = false, $error = true)
{

このようにモデルで引数を受け取れるようにし、

    $resource = db_insert($queries, $return, $error);
    if (!$resource) {
        return $resource;
    }

モデル内部から db_insert() などに引数をそのまま渡すようにします。これで

$resource = insert_categories(array(
    'values' => array(
        'id' => 2,  // 重複しているとする
    ),
), array(), false, false);  // 第3・第4引数を false にすると自動でのエラー表示が無効になる

if (!$resource) {
    error('データを登録できません : ' . db_error());  // 任意のエラー処理
}

このように処理できるようになります。

データベースのバージョン管理

複数人でプログラムを作成したり複数箇所でプログラムを実行したりする場合のために、データベースのバージョン管理(マイグレーション)を行えます。これにより、各環境でデータベースの定義内容を同じ状態に保つことができます。

この機能を使用する場合、まずはデータベースに接続できるようにしておきます。

次に index.php と同じ場所(config.phpDATABASE_MIGRATE_PATH の値を変更した場合はその場所)に migrate ディレクトリを作成し、http://www.example.com/index.php/?_mode=db_migrate にアクセスすると migrate/ 内に置いたSQLファイルが実行されます。

SQLファイルは YYYYMMDDHHIISS-任意の半角英数字.sql という名前にします。YYYYMMDDHHIISS はバージョン番号として扱われるため、重複しないようにします。具体的には以下のような名前にします。

20151128213500-create_table_classes.sql
20151128214000-create_table_members.sql

初回実行時、バージョン管理用のテーブルが levis_migrations という名前で作成されます。実行したファイルはこのテーブルに記録されるため、何度も同じSQLファイルが実行されることはありません。

データベースのバージョン管理ではテーブルの構造のみ管理し、初期データは含めないことを推奨します。一例ですが初期データの登録のような管理を推奨します。

初期データの登録

データベースのバージョン管理を行う場合、初期データはバージョン管理に含めないことを推奨します。初期データを管理対象にすると、

などの問題があるため、別途SQLファイルを用意したり以下のようにデータ登録プログラムを用意することを推奨します。

一例ですが /app/controllers/seed/companies.php という名前でコントローラを作成し、以下の内容を記述します。

<?php

import('libs/plugins/hash.php');

// トランザクションを開始
db_transaction();

// 企業を確認
$companies = select_companies(array(
    'select' => 'id',
    'where'  => array(
        'code = :code',
        array(
            'code' => 'sample',
        ),
    ),
));
if (count($companies) >= 1) {
    error('すでに登録されています。');
}

// 企業を登録
$resource = insert_companies(array(
    'values' => array(
        'code' => 'sample',           // 企業コード
        'name' => '株式会社サンプル', // 企業名
        'tel'  => '0120-0000-0000',   // 電話番号
    ),
));
if (!$resource) {
    error('企業を登録できません。');
}

$company_id = db_last_insert_id();

// 従業員を登録
$password_salt = hash_salt();
$password      = hash_crypt('Hi2EbNPaeS', $password_salt . ':' . $GLOBALS['config']['hash_salt']);

$resource = insert_employees(array(
    'values' => array(
        'username'      => 'yamada',       // ユーザ名
        'password'      => $password,      // パスワード
        'password_salt' => $password_salt, // パスワードのソルト
        'name'          => '山田太郎',     // 名前
        'company_id'    => $company_id,    // 外部キー 企業
        'loggedin'      => null,           // 最終ログイン日時
    ),
));
if (!$resource) {
    error('従業員を登録できません。');
}

// トランザクションを終了
db_commit();

ok();

これで /seed/companies にアクセスすると、必要な初期データが登録されます。

このコントローラは非公開が望ましいため、一例ですが /app/controllers/before_seed.php を作成して

<?php

// アクセス元を確認
if (clientip() !== '203.0.113.1') {
    exit('不正なアクセスです。');
}

このように記述することにより、アクセス制限を行っておきます。

データベースのバックアップ

データベースのバックアップをサーバ上に作成できます。

この機能を使用する場合、まずはデータベースに接続できるようにしておきます。

次に index.php と同じ場所(config.phpDATABASE_BACKUP_PATH の値を変更した場合はその場所)に backup ディレクトリを作成し、http://www.example.com/index.php/?_mode=db_backup にアクセスするとバックアップ画面が表示されます。「backup」ボタンを押すと backup/ 内にSQLファイルが保存されます。

バックアップが有効になっていると、データベースのバージョン管理でデータベースに変更を加える直前に自動でバックアップが作成されます。

フレームワーク本体の処理内容に手を加える

app/ 直下に特定の名前でファイルを置いておくと、フレームワーク本体から読み込まれます。これを利用して、フレームワークの挙動を調整できます。

例えば app/database.php に以下の処理を書くと、他データベースへの接続準備になります。これで、モデルやコントローラから db_connect('master'); と書くだけで他のデータベースに接続できます。

<?php

db_connect(array(
    'master' => array(
        'host'     => 'localhost',
        'username' => 'root',
        'password' => '1234',
        'name'     => 'master_db',
    ),
));

db_connect('default');

例えば app/routing.php に以下の処理を書くと、URLルーティングのルールを変更できます。これで index.php/test/param2/param3 でアクセスした時、param2param3 の値によってコントローラなどが呼び出されるようになります。

<?php

if (isset($_params[0]) && $_params[0] === 'test') {
    if (isset($_params[1])) {
        $_REQUEST['_mode'] = empty($_params[1]) ? 'home' : $_params[1];
    }
    if (isset($_params[2])) {
        $_REQUEST['_work'] = empty($_params[2]) ? 'index' : $_params[2];
    }
}

フレームワーク外からフレームワークの命令を利用

定数 MAIN_PATH にlevisの配置場所を設定し、libs/cores/loader.php を読み込むとフレームワーク外からフレームワークの命令を利用できます。

loader.php を読み込むことによりフレームワークの基本的な命令は使えるようになりますが、モデル・ビュー・コントローラ・サービスは明示的に読み込む必要があります。model() と書くとすべてのモデルを、service() と書くとすべてのサービスを、一括で読み込みます。

<?php

if (!defined('MAIN_PATH')) {
    define('MAIN_PATH', '/var/www/html/test/levis/');
}
require_once MAIN_PATH . 'libs/cores/loader.php';

// 教室を取得
model('classes.php');
$classes = select_classes(array(
    'order_by' => 'id',
));

// 名簿を取得
model('members.php');
$members = select_members(array(
    'order_by' => 'id',
));

?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>テスト</title>
    </head>
    <body>
        <p>テスト。</p>
        <ul>
            <?php foreach ($classes as $class) : ?>
            <li><?php h($class['name']) ?></li>
            <?php endforeach ?>
        </ul>
        <ul>
            <?php foreach ($members as $member) : ?>
            <li><?php h($member['name']) ?></li>
            <?php endforeach ?>
        </ul>
    </body>
</html>
<?php

if (!defined('MAIN_PATH')) {
    define('MAIN_PATH', '/var/www/html/test/levis/');
}
require_once MAIN_PATH . 'libs/cores/loader.php';

// ページを表示
model('classes.php');
controller('home/index.php');
view('home/index.php');
<?php

if (!defined('MAIN_PATH')) {
    define('MAIN_PATH', '/var/www/html/test/levis/');
}
require_once MAIN_PATH . 'libs/cores/loader.php';

// 引数付きでページを表示
model();
$_params = array('class', 'class1');
controller('class/index.php');
view('class/index.php');
<?php

if (!defined('MAIN_PATH')) {
    define('MAIN_PATH', '/var/www/html/test/levis/');
}
require_once MAIN_PATH . 'libs/cores/loader.php';

// ページを取得
model('classes.php');
controller('home/index.php');
echo view('home/index.php', true);

通常ページを作成

index.php と同じ場所(config.phpPAGE_PATH の値を変更した場合はその場所)に page ディレクトリを作成し、その中にPHPファイルを作成すると通常ページとして扱われます。

つまり、例えば page/test.php を作成すると、/index.php/test(もしくは /test) にアクセスしたとき、フレームワークを経由してこのページが表示されます。フレームワークとURL規則を統一した通常ページを作成したり、プログラムへのログイン状態に応じて通常ページの表示内容を切り替えたりする場合に利用できます。

app/controllers/page.php があると、通常ページ共通のコントローラとして扱われます。

cronで実行する

フレームワーク外からフレームワークの命令を利用の方法でプログラムを作成し、そのプログラムをcronで呼び出します。

例えば check_update.php を作成した場合、一例ですがcronでは以下のように指定します。

*/5 * * * * apache /usr/bin/php /var/www/levis/task/check_update.php

雛形作成(Scaffold)

データベースにテーブルを作成し、index.php と同じ場所(config.phpDATABASE_SCAFFOLD_PATH の値を変更した場合はその場所)に scaffold ディレクトリを作成し、http://www.example.com/index.php/?_mode=db_scaffold にアクセスすると scaffold/app/ ディレクトリ内にプログラムの雛形が作成されます。

scaffold/app/app/ に移動させると、そのままプログラムのモデル・ビュー・コントローラとして利用できます。(ただし即座に公開できるプログラムというより、プログラム作成の補助に使うためのコードという位置づけで作成しています。)

scaffold/test/ には単体テスト用のプログラムも自動作成されます。

単体テスト(UnitTest)

index.php と同じ場所(config.phpTEST_PATH の値を変更した場合はその場所)に test ディレクトリを作成し、その中にテスト用プログラム(calculate.php など、ファイル名は任意。)を作成し、http://www.example.com/index.php/?_mode=test_index にアクセスすると単体テストを行えます。ページ内に表示される All Test. リンクから、一括テストも行えます。

テスト用プログラムは、具体的には以下のような内容になります。

<?php

// 掛け算の結果をテスト(「multiplication 1」のみ成功する)
test_equals('multiplication 1', multiplication(4, 2), 8);
test_equals('multiplication 2', multiplication(4, 2), 6);
test_equals('multiplication 3', multiplication(4, 2), 4);

// 割り算の結果をテスト(「division 3」のみ成功する)
test_equals('division 1', division(4, 2), 6);
test_equals('division 2', division(4, 2), 4);
test_equals('division 3', division(4, 2), 2);

// テスト用の関数
function multiplication($x, $y) {
    return $x * $y;
}
function division($x, $y) {
    return $x / $y;
}
<?php

// 返り値に特定の文字列が含まれるかテスト(「message 1」のみ成功する)
test_contains('message 1', message('test'), 'test');
test_contains('message 2', message('test'), 'sample');

// テスト用の関数
function message($message) {
    return 'This is a \'' . $message . '\'!';
}
<?php

// 返り値に特定の形式の文字列が含まれるかテスト
test_regexp('check_date', check_date(), '\d\d\d\d-\d\d-\d\d');

// テスト用の関数
function check_date() {
    return date('Y-m-d');
}
<?php

// トップページに「テスト」という文字が含まれているかテスト
model('classes.php');
controller('home/index.php');
$html = view('home/index.php', true);
test_contains('home/index', $html, 'テスト');

// index.php/class/class1に「テスト」という文字が含まれているかテスト
model();
$_params = array('class', 'class1');
controller('class/index.php');
$html = view('class/index.php', true);
test_contains('class/index', $html, 'テスト');

テスト用の関数は libs/cores/test.php 内で定義されており、以下を利用できます。

test_equals('テストの説明', '実際の値', '期待する値')  // 「実際の値」と「期待する値」が等しければテスト成功とみなします。
test_not_equals('テストの説明', '実際の値', '期待する値')  // 「実際の値」と「期待する値」が等しくなければテスト成功とみなします。
test_greaterthan('テストの説明', '実際の値', '期待する値')  // 「実際の値」が「期待する値」より大きければテスト成功とみなします。
test_greaterthanorequal('テストの説明', '実際の値', '期待する値')  // 「実際の値」が「期待する値」以上ならばテスト成功とみなします。
test_lessthan('テストの説明', '実際の値', '期待する値')  // 「実際の値」が「期待する値」より小さければテスト成功とみなします。
test_lessthanorequal('テストの説明', '実際の値', '期待する値')  // 「実際の値」が「期待する値」以下ならばテスト成功とみなします。
test_contains('テストの説明', '実際の値', '期待する値')  // 「実際の値」に「期待する値」が含まれていればテスト成功とみなします。
test_not_contains('テストの説明', '実際の値', '期待する値')  // 「実際の値」に「期待する値」が含まれていなければテスト成功とみなします。
test_regexp('テストの説明', '実際の値', '期待する値')  // 「実際の値」と「期待する値」の正規表現にマッチすればテスト成功とみなします。
test_not_regexp('テストの説明', '実際の値', '期待する値')  // 「実際の値」と「期待する値」の正規表現にマッチしなければテスト成功とみなします。
test_array_haskey('テストの説明', '配列', '期待する配列のキー')  // 「配列」に「期待する配列のキー」が存在すればテスト成功とみなします。
test_array_not_haskey('テストの説明', '配列', '期待する配列のキー')  // 「配列」に「期待する配列のキー」が存在しなければテスト成功とみなします。
test_array_subset('テストの説明', '配列', '期待する配列の値')  // 「配列」と「期待する配列の値」が存在すればテスト成功とみなします。
test_array_not_subset('テストの説明', '配列', '期待する配列の値')  // 「配列」と「期待する配列の値」が存在しなければテスト成功とみなします。
Copyright © 2002-2017 refirio.org, All rights reserved.