levis: PHP Framework 説明書

エトセトラ

目次に戻る

補足

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

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

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

token() でトークンの発行と確認ができます。

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

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

ユーザー認証の実装

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

レイアウト

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

フィルター

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

ORM・アソシエーション

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

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

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

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

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

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

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

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

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

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

  1. 本番環境か否かでプログラムを分岐させたい場合、​PRODUCTION という定数が定義済みか否かで判定する。同様に、検収環境か否かは ​STAGING という定数で判定する。どちらも定義されていなければ開発環境とする。
  2. フレームワークの config.phpconfig.php.default という名前で保存しておき、環境に依存しない内容のみ設定しておく。config.php はリポジトリの管理対象外にしておく。
  3. プログラムを動作させる場合、各々の環境で config.php.default を複製して config.php を作成し、環境に応じた設定を行う。
  4. 本番環境なら ​PRODUCTION という定数を、検収環境なら ​STAGING という定数を config.php 内で定義する。

予約語一覧

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

以下の変数。

以下のリクエスト変数。

以下のグローバル変数。

以下のセッション変数。

以下の関数。

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

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

この機能はVer6.1以降で対応しています。

グローバル変数 $GLOBALS['core']['target'] に値を代入すると、import() でファイルを読み込む際の場所として認識されます。

解説作成中。

adminルーティング

この機能はVer6.6以降で対応しています。

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

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

<?php

if (isset($params[0]) && preg_match('/^admin$/', $params[0])) {
    $GLOBALS['core']['routing'] = $params[0];

    $_REQUEST['mode'] = empty($params[1]) ? 'home'  : $params[1];
    $_REQUEST['work'] = empty($params[2]) ? 'index' : $params[2];
}

具体的には、

となります。管理ページが非常に多機能な場合など、処理をフォルダごとに分けることができるようになります。

admin の部分などを変更すれば、ルーティングのルールは自由に変更できます。

app/routing.php の詳細については、フレームワーク本体の処理内容に手を加えるを参照してください。

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

この機能はVer6.6以降で対応しています。

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

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

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

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

$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
データベースから出力するときの変換後文字コード

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

この機能はVer6以降で対応しています。

複数人でプログラムを作成したり複数箇所でプログラムを実行したりする場合のために、データベースのバージョン管理(マイグレーション)を行えます。

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

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

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

20151128210200-create_table_address.sql
20151128210201-insert_into_address.sql

初回実行時、バージョン管理用のテーブルが levis_migrations という名前で作成されます。実行したファイルはこのテーブルに記録されるため、何度も同じSQLファイルが実行されることはありません。この仕組みを利用すれば、各環境でデータベースの定義内容を同じ状態に保つことができます。

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

この機能はVer7以降で対応しています。

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

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

次に 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

define('MAIN_PATH', '/var/www/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

define('MAIN_PATH', '/var/www/levis/');
require_once MAIN_PATH . 'libs/cores/loader.php';

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

define('MAIN_PATH', '/var/www/levis/');
require_once MAIN_PATH . 'libs/cores/loader.php';

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

define('MAIN_PATH', '/var/www/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で呼び出します。

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

*/5 * * * * webmaster /usr/bin/php /var/www/html/cron.php

雛形作成(Scaffold)

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

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

Ver6以降では、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-2016 refirio.org, All rights reserved.